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,214 +1,203 @@
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>
/// A base implementation of an Application service containing a worker task that performs background processing.
/// </summary>
/// <example>
/// The following code describes how to implement the <see cref="AppWorkerBase"/> class.
/// <code>
/// using System;
/// using System.Threading.Tasks;
/// using Unosquare.Swan;
/// using Unosquare.Swan.Abstractions;
///
/// class Worker : AppWorkerBase
/// {
/// // an action that will be executed if the worker is stopped
/// public Action OnExit { get; set; }
///
/// // override the base loop method, this is the code will
/// // execute until the cancellation token is canceled.
/// protected override Task WorkerThreadLoop()
/// {
/// // delay a second and then proceed
/// await Task.Delay(TimeSpan.FromMilliseconds(1000), CancellationToken);
///
/// // just print out this
/// $"Working...".WriteLine();
/// }
///
/// // Once the worker is stopped this code will be executed
/// protected override void OnWorkerThreadExit()
/// {
/// // execute the base method
/// base.OnWorkerThreadExit();
///
/// // then if the OnExit Action is not null execute it
/// OnExit?.Invoke();
/// }
/// }
/// </code>
/// </example>
public abstract class AppWorkerBase
: IWorker, IDisposable {
private readonly Object _syncLock = new Object();
private AppWorkerState _workerState = AppWorkerState.Stopped;
private CancellationTokenSource _tokenSource;
/// <summary> /// <summary>
/// A base implementation of an Application service containing a worker task that performs background processing. /// Initializes a new instance of the <see cref="AppWorkerBase"/> class.
/// </summary> /// </summary>
/// <example> protected AppWorkerBase() {
/// The following code describes how to implement the <see cref="AppWorkerBase"/> class. this.State = AppWorkerState.Stopped;
/// <code> this.IsBusy = false;
/// using System; }
/// using System.Threading.Tasks;
/// using Unosquare.Swan; /// <summary>
/// using Unosquare.Swan.Abstractions; /// Occurs when [state changed].
/// /// </summary>
/// class Worker : AppWorkerBase public event EventHandler<AppWorkerStateChangedEventArgs> StateChanged;
/// {
/// // an action that will be executed if the worker is stopped #region Properties
/// public Action OnExit { get; set; }
/// /// <summary>
/// // override the base loop method, this is the code will /// Gets the state of the application service.
/// // execute until the cancellation token is canceled. /// In other words, useful to know whether the service is running.
/// protected override Task WorkerThreadLoop() /// </summary>
/// { /// <value>
/// // delay a second and then proceed /// The state.
/// await Task.Delay(TimeSpan.FromMilliseconds(1000), CancellationToken); /// </value>
/// public AppWorkerState State {
/// // just print out this get => this._workerState;
/// $"Working...".WriteLine();
/// } private set {
/// lock(this._syncLock) {
/// // Once the worker is stopped this code will be executed if(value == this._workerState) {
/// protected override void OnWorkerThreadExit() return;
/// { }
/// // execute the base method
/// base.OnWorkerThreadExit(); $"Service state changing from {this.State} to {value}".Debug(this.GetType().Name);
/// AppWorkerState newState = value;
/// // then if the OnExit Action is not null execute it AppWorkerState oldState = this._workerState;
/// OnExit?.Invoke(); this._workerState = value;
/// }
/// } StateChanged?.Invoke(this, new AppWorkerStateChangedEventArgs(oldState, newState));
/// </code> }
/// </example> }
public abstract class AppWorkerBase }
: IWorker, IDisposable
{ /// <summary>
private readonly object _syncLock = new object(); /// Gets the cancellation token.
private AppWorkerState _workerState = AppWorkerState.Stopped; /// </summary>
private CancellationTokenSource _tokenSource; /// <value>
/// The cancellation token.
/// <summary> /// </value>
/// Initializes a new instance of the <see cref="AppWorkerBase"/> class. public CancellationToken CancellationToken => this._tokenSource?.Token ?? default;
/// </summary>
protected AppWorkerBase() /// <summary>
{ /// Gets a value indicating whether the thread is busy.
State = AppWorkerState.Stopped; /// </summary>
IsBusy = false; /// <value>
} /// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
/// </value>
/// <summary> public Boolean IsBusy {
/// Occurs when [state changed]. get; private set;
/// </summary> }
public event EventHandler<AppWorkerStateChangedEventArgs> StateChanged;
#endregion
#region Properties
#region AppWorkerBase Methods
/// <summary>
/// Gets the state of the application service. /// <summary>
/// In other words, useful to know whether the service is running. /// Performs internal service initialization tasks required before starting the service.
/// </summary> /// </summary>
/// <value> /// <exception cref="InvalidOperationException">Service cannot be initialized because it seems to be currently running.</exception>
/// The state. public virtual void Initialize() => this.CheckIsRunning();
/// </value>
public AppWorkerState State /// <inheritdoc/>
{ /// <exception cref="InvalidOperationException">Service cannot be started because it seems to be currently running.</exception>
get => _workerState; public virtual void Start() {
this.CheckIsRunning();
private set
{ this.CreateWorker();
lock (_syncLock) this.State = AppWorkerState.Running;
{ }
if (value == _workerState) return;
/// <inheritdoc/>
$"Service state changing from {State} to {value}".Debug(GetType().Name); /// <exception cref="InvalidOperationException">Service cannot be stopped because it is not running.</exception>
var newState = value; public virtual void Stop() {
var oldState = _workerState; if(this.State != AppWorkerState.Running) {
_workerState = value; return;
}
StateChanged?.Invoke(this, new AppWorkerStateChangedEventArgs(oldState, newState));
} this._tokenSource?.Cancel();
} "Service stop requested.".Debug(this.GetType().Name);
} this.State = AppWorkerState.Stopped;
}
/// <summary>
/// Gets the cancellation token. /// <inheritdoc />
/// </summary> public void Dispose() => this._tokenSource?.Dispose();
/// <value>
/// The cancellation token. #endregion
/// </value>
public CancellationToken CancellationToken => _tokenSource?.Token ?? default; #region Abstract and Virtual Methods
/// <summary> /// <summary>
/// Gets a value indicating whether the thread is busy. /// Called when an unhandled exception is thrown.
/// </summary> /// </summary>
/// <value> /// <param name="ex">The ex.</param>
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>. protected virtual void OnWorkerThreadLoopException(Exception ex)
/// </value> => "Service exception detected.".Debug(this.GetType().Name, ex);
public bool IsBusy { get; private set; }
/// <summary>
#endregion /// This method is called when the user loop has exited.
/// </summary>
#region AppWorkerBase Methods protected virtual void OnWorkerThreadExit() => "Service thread is stopping.".Debug(this.GetType().Name);
/// <summary> /// <summary>
/// Performs internal service initialization tasks required before starting the service. /// Implement this method as a loop that checks whether CancellationPending has been set to true
/// </summary> /// If so, immediately exit the loop.
/// <exception cref="InvalidOperationException">Service cannot be initialized because it seems to be currently running.</exception> /// </summary>
public virtual void Initialize() => CheckIsRunning(); /// <returns>A task representing the execution of the worker.</returns>
protected abstract Task WorkerThreadLoop();
/// <inheritdoc/>
/// <exception cref="InvalidOperationException">Service cannot be started because it seems to be currently running.</exception> private void CheckIsRunning() {
public virtual void Start() if(this.State != AppWorkerState.Stopped) {
{ throw new InvalidOperationException("Service cannot be initialized because it seems to be currently running.");
CheckIsRunning(); }
}
CreateWorker();
State = AppWorkerState.Running; private void CreateWorker() {
} this._tokenSource = new CancellationTokenSource();
_ = this._tokenSource.Token.Register(() => {
/// <inheritdoc/> this.IsBusy = false;
/// <exception cref="InvalidOperationException">Service cannot be stopped because it is not running.</exception> this.OnWorkerThreadExit();
public virtual void Stop() });
{
if (State != AppWorkerState.Running) _ = Task.Run(async () => {
return; this.IsBusy = true;
_tokenSource?.Cancel(); try {
"Service stop requested.".Debug(GetType().Name); while(!this.CancellationToken.IsCancellationRequested) {
State = AppWorkerState.Stopped; await this.WorkerThreadLoop().ConfigureAwait(false);
} }
} catch(AggregateException) {
/// <inheritdoc /> // Ignored
public void Dispose() => _tokenSource?.Dispose(); } catch(Exception ex) {
ex.Log(this.GetType().Name);
#endregion this.OnWorkerThreadLoopException(ex);
#region Abstract and Virtual Methods if(!this._tokenSource.IsCancellationRequested) {
this._tokenSource.Cancel();
/// <summary> }
/// Called when an unhandled exception is thrown. }
/// </summary> },
/// <param name="ex">The ex.</param> this._tokenSource.Token);
protected virtual void OnWorkerThreadLoopException(Exception ex) }
=> "Service exception detected.".Debug(GetType().Name, ex);
#endregion
/// <summary> }
/// This method is called when the user loop has exited.
/// </summary>
protected virtual void OnWorkerThreadExit() => "Service thread is stopping.".Debug(GetType().Name);
/// <summary>
/// Implement this method as a loop that checks whether CancellationPending has been set to true
/// If so, immediately exit the loop.
/// </summary>
/// <returns>A task representing the execution of the worker.</returns>
protected abstract Task WorkerThreadLoop();
private void CheckIsRunning()
{
if (State != AppWorkerState.Stopped)
throw new InvalidOperationException("Service cannot be initialized because it seems to be currently running.");
}
private void CreateWorker()
{
_tokenSource = new CancellationTokenSource();
_tokenSource.Token.Register(() =>
{
IsBusy = false;
OnWorkerThreadExit();
});
Task.Run(async () =>
{
IsBusy = true;
try
{
while (!CancellationToken.IsCancellationRequested)
{
await WorkerThreadLoop().ConfigureAwait(false);
}
}
catch (AggregateException)
{
// Ignored
}
catch (Exception ex)
{
ex.Log(GetType().Name);
OnWorkerThreadLoopException(ex);
if (!_tokenSource.IsCancellationRequested)
_tokenSource.Cancel();
}
},
_tokenSource.Token);
}
#endregion
}
} }

View File

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

View File

@ -1,204 +1,202 @@
namespace Unosquare.Swan.Components using System;
{ using System.Runtime.InteropServices;
using System;
using System.Runtime.InteropServices; namespace Unosquare.Swan.Components {
/// <summary>
/// A fixed-size buffer that acts as an infinite length one.
/// This buffer is backed by unmanaged, very fast memory so ensure you call
/// the dispose method when you are done using it.
/// Only for Windows.
/// </summary>
/// <seealso cref="System.IDisposable" />
public sealed class CircularBuffer : IDisposable {
private readonly Object _syncLock = new Object();
private IntPtr _buffer = IntPtr.Zero;
/// <summary> /// <summary>
/// A fixed-size buffer that acts as an infinite length one. /// Initializes a new instance of the <see cref="CircularBuffer"/> class.
/// This buffer is backed by unmanaged, very fast memory so ensure you call
/// the dispose method when you are done using it.
/// Only for Windows.
/// </summary> /// </summary>
/// <seealso cref="System.IDisposable" /> /// <param name="bufferLength">Length of the buffer.</param>
public sealed class CircularBuffer : IDisposable public CircularBuffer(Int32 bufferLength) {
{
private readonly object _syncLock = new object();
private IntPtr _buffer = IntPtr.Zero;
/// <summary>
/// Initializes a new instance of the <see cref="CircularBuffer"/> class.
/// </summary>
/// <param name="bufferLength">Length of the buffer.</param>
public CircularBuffer(int 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
/// <summary> /// <summary>
/// Gets the capacity of this buffer. /// Gets the capacity of this buffer.
/// </summary> /// </summary>
/// <value> /// <value>
/// The length. /// The length.
/// </value> /// </value>
public int Length { get; private set; } public Int32 Length {
get; private set;
/// <summary> }
/// Gets the current, 0-based read index.
/// </summary> /// <summary>
/// <value> /// Gets the current, 0-based read index.
/// The index of the read. /// </summary>
/// </value> /// <value>
public int ReadIndex { get; private set; } /// The index of the read.
/// </value>
/// <summary> public Int32 ReadIndex {
/// Gets the current, 0-based write index. get; private set;
/// </summary> }
/// <value>
/// The index of the write. /// <summary>
/// </value> /// Gets the current, 0-based write index.
public int WriteIndex { get; private set; } /// </summary>
/// <value>
/// <summary> /// The index of the write.
/// Gets an the object associated with the last write. /// </value>
/// </summary> public Int32 WriteIndex {
/// <value> get; private set;
/// The write tag. }
/// </value>
public TimeSpan WriteTag { get; private set; } = TimeSpan.MinValue; /// <summary>
/// Gets an the object associated with the last write.
/// <summary> /// </summary>
/// Gets the available bytes to read. /// <value>
/// </summary> /// The write tag.
/// <value> /// </value>
/// The readable count. public TimeSpan WriteTag { get; private set; } = TimeSpan.MinValue;
/// </value>
public int ReadableCount { get; private set; } /// <summary>
/// Gets the available bytes to read.
/// <summary> /// </summary>
/// Gets the number of bytes that can be written. /// <value>
/// </summary> /// The readable count.
/// <value> /// </value>
/// The writable count. public Int32 ReadableCount {
/// </value> get; private set;
public int WritableCount => Length - ReadableCount; }
/// <summary> /// <summary>
/// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0). /// Gets the number of bytes that can be written.
/// </summary> /// </summary>
/// <value> /// <value>
/// The capacity percent. /// The writable count.
/// </value> /// </value>
public double CapacityPercent => 1.0 * ReadableCount / Length; public Int32 WritableCount => this.Length - this.ReadableCount;
#endregion /// <summary>
/// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0).
#region Methods /// </summary>
/// <value>
/// <summary> /// The capacity percent.
/// Reads the specified number of bytes into the target array. /// </value>
/// </summary> public Double CapacityPercent => 1.0 * this.ReadableCount / this.Length;
/// <param name="requestedBytes">The requested bytes.</param>
/// <param name="target">The target.</param> #endregion
/// <param name="targetOffset">The target offset.</param>
/// <exception cref="System.InvalidOperationException"> #region Methods
/// Exception that is thrown when a method call is invalid for the object's current state.
/// </exception> /// <summary>
public void Read(int requestedBytes, byte[] target, int targetOffset) /// Reads the specified number of bytes into the target array.
{ /// </summary>
lock (_syncLock) /// <param name="requestedBytes">The requested bytes.</param>
{ /// <param name="target">The target.</param>
if (requestedBytes > ReadableCount) /// <param name="targetOffset">The target offset.</param>
{ /// <exception cref="System.InvalidOperationException">
throw new InvalidOperationException( /// Exception that is thrown when a method call is invalid for the object's current state.
$"Unable to read {requestedBytes} bytes. Only {ReadableCount} bytes are available"); /// </exception>
} public void Read(Int32 requestedBytes, Byte[] target, Int32 targetOffset) {
lock(this._syncLock) {
var readCount = 0; if(requestedBytes > this.ReadableCount) {
while (readCount < requestedBytes) throw new InvalidOperationException(
{ $"Unable to read {requestedBytes} bytes. Only {this.ReadableCount} bytes are available");
var copyLength = Math.Min(Length - ReadIndex, requestedBytes - readCount); }
var sourcePtr = _buffer + ReadIndex;
Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength); Int32 readCount = 0;
while(readCount < requestedBytes) {
readCount += copyLength; Int32 copyLength = Math.Min(this.Length - this.ReadIndex, requestedBytes - readCount);
ReadIndex += copyLength; IntPtr sourcePtr = this._buffer + this.ReadIndex;
ReadableCount -= copyLength; Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength);
if (ReadIndex >= Length) readCount += copyLength;
ReadIndex = 0; this.ReadIndex += copyLength;
} this.ReadableCount -= copyLength;
}
} if(this.ReadIndex >= this.Length) {
this.ReadIndex = 0;
/// <summary> }
/// Writes data to the backing buffer using the specified pointer and length. }
/// and associating a write tag for this operation. }
/// </summary> }
/// <param name="source">The source.</param>
/// <param name="length">The length.</param> /// <summary>
/// <param name="writeTag">The write tag.</param> /// Writes data to the backing buffer using the specified pointer and length.
/// <exception cref="System.InvalidOperationException">Unable to write to circular buffer. Call the Read method to make some additional room.</exception> /// and associating a write tag for this operation.
public void Write(IntPtr source, int length, TimeSpan writeTag) /// </summary>
{ /// <param name="source">The source.</param>
lock (_syncLock) /// <param name="length">The length.</param>
{ /// <param name="writeTag">The write tag.</param>
if (ReadableCount + length > Length) /// <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, Int32 length, TimeSpan writeTag) {
throw new InvalidOperationException( lock(this._syncLock) {
$"Unable to write to circular buffer. Call the {nameof(Read)} method to make some additional room."); if(this.ReadableCount + length > this.Length) {
} throw new InvalidOperationException(
$"Unable to write to circular buffer. Call the {nameof(Read)} method to make some additional room.");
var writeCount = 0; }
while (writeCount < length)
{ Int32 writeCount = 0;
var copyLength = Math.Min(Length - WriteIndex, length - writeCount); while(writeCount < length) {
var sourcePtr = source + writeCount; Int32 copyLength = Math.Min(this.Length - this.WriteIndex, length - writeCount);
var targetPtr = _buffer + WriteIndex; IntPtr sourcePtr = source + writeCount;
CopyMemory(targetPtr, sourcePtr, (uint) copyLength); IntPtr targetPtr = this._buffer + this.WriteIndex;
CopyMemory(targetPtr, sourcePtr, (UInt32)copyLength);
writeCount += copyLength;
WriteIndex += copyLength; writeCount += copyLength;
ReadableCount += copyLength; this.WriteIndex += copyLength;
this.ReadableCount += copyLength;
if (WriteIndex >= Length)
WriteIndex = 0; if(this.WriteIndex >= this.Length) {
} this.WriteIndex = 0;
}
WriteTag = writeTag; }
}
} this.WriteTag = writeTag;
}
/// <summary> }
/// Resets all states as if this buffer had just been created.
/// </summary> /// <summary>
public void Clear() /// Resets all states as if this buffer had just been created.
{ /// </summary>
lock (_syncLock) public void Clear() {
{ lock(this._syncLock) {
WriteIndex = 0; this.WriteIndex = 0;
ReadIndex = 0; this.ReadIndex = 0;
WriteTag = TimeSpan.MinValue; this.WriteTag = TimeSpan.MinValue;
ReadableCount = 0; this.ReadableCount = 0;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._buffer == IntPtr.Zero) {
if (_buffer == IntPtr.Zero) return; return;
}
Marshal.FreeHGlobal(_buffer);
_buffer = IntPtr.Zero; Marshal.FreeHGlobal(this._buffer);
Length = 0; this._buffer = IntPtr.Zero;
} this.Length = 0;
}
/// <summary>
/// Fast pointer memory block copy function. /// <summary>
/// </summary> /// Fast pointer memory block copy function.
/// <param name="destination">The destination.</param> /// </summary>
/// <param name="source">The source.</param> /// <param name="destination">The destination.</param>
/// <param name="length">The length.</param> /// <param name="source">The source.</param>
[DllImport("kernel32")] /// <param name="length">The length.</param>
public static extern void CopyMemory(IntPtr destination, IntPtr source, uint length); [DllImport("kernel32")]
public static extern void CopyMemory(IntPtr destination, IntPtr source, UInt32 length);
#endregion
} #endregion
}
} }

View File

@ -1,109 +1,101 @@
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>
/// Represents a CsProjFile (and FsProjFile) parser.
/// </summary>
/// <remarks>
/// Based on https://github.com/maartenba/dotnetcli-init.
/// </remarks>
/// <typeparam name="T">The type of <c>CsProjMetadataBase</c>.</typeparam>
/// <seealso cref="System.IDisposable" />
public class CsProjFile<T>
: IDisposable
where T : CsProjMetadataBase {
private readonly Stream _stream;
private readonly Boolean _leaveOpen;
private readonly XDocument _xmlDocument;
/// <summary> /// <summary>
/// Represents a CsProjFile (and FsProjFile) parser. /// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
/// </summary> /// </summary>
/// <remarks> /// <param name="filename">The filename.</param>
/// Based on https://github.com/maartenba/dotnetcli-init. public CsProjFile(String filename = null)
/// </remarks> : this(OpenFile(filename)) {
/// <typeparam name="T">The type of <c>CsProjMetadataBase</c>.</typeparam> // placeholder
/// <seealso cref="System.IDisposable" /> }
public class CsProjFile<T>
: IDisposable /// <summary>
where T : CsProjMetadataBase /// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
{ /// </summary>
private readonly Stream _stream; /// <param name="stream">The stream.</param>
private readonly bool _leaveOpen; /// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
private readonly XDocument _xmlDocument; /// <exception cref="ArgumentException">Project file is not of the new .csproj type.</exception>
public CsProjFile(Stream stream, Boolean leaveOpen = false) {
/// <summary> this._stream = stream;
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class. this._leaveOpen = leaveOpen;
/// </summary>
/// <param name="filename">The filename.</param> this._xmlDocument = XDocument.Load(stream);
public CsProjFile(string filename = null)
: this(OpenFile(filename)) XElement projectElement = this._xmlDocument.Descendants("Project").FirstOrDefault();
{ XAttribute sdkAttribute = projectElement?.Attribute("Sdk");
// placeholder String sdk = sdkAttribute?.Value;
} if(sdk != "Microsoft.NET.Sdk" && sdk != "Microsoft.NET.Sdk.Web") {
throw new ArgumentException("Project file is not of the new .csproj type.");
/// <summary> }
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
/// </summary> this.Metadata = Activator.CreateInstance<T>();
/// <param name="stream">The stream.</param> this.Metadata.SetData(this._xmlDocument);
/// <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>
public CsProjFile(Stream stream, bool leaveOpen = false) /// <summary>
{ /// Gets the metadata.
_stream = stream; /// </summary>
_leaveOpen = leaveOpen; /// <value>
/// The nu get metadata.
_xmlDocument = XDocument.Load(stream); /// </value>
public T Metadata {
var projectElement = _xmlDocument.Descendants("Project").FirstOrDefault(); get;
var sdkAttribute = projectElement?.Attribute("Sdk"); }
var sdk = sdkAttribute?.Value;
if (sdk != "Microsoft.NET.Sdk" && sdk != "Microsoft.NET.Sdk.Web") /// <summary>
{ /// Saves this instance.
throw new ArgumentException("Project file is not of the new .csproj type."); /// </summary>
} public void Save() {
this._stream.SetLength(0);
Metadata = Activator.CreateInstance<T>(); this._stream.Position = 0;
Metadata.SetData(_xmlDocument);
} this._xmlDocument.Save(this._stream);
}
/// <summary>
/// Gets the metadata. /// <inheritdoc />
/// </summary> public void Dispose() {
/// <value> if(!this._leaveOpen) {
/// The nu get metadata. this._stream?.Dispose();
/// </value> }
public T Metadata { get; } }
/// <summary> private static FileStream OpenFile(String filename) {
/// Saves this instance. if(filename == null) {
/// </summary> filename = Directory
public void Save() .EnumerateFiles(Directory.GetCurrentDirectory(), "*.csproj", SearchOption.TopDirectoryOnly)
{ .FirstOrDefault();
_stream.SetLength(0); }
_stream.Position = 0;
if(filename == null) {
_xmlDocument.Save(_stream); filename = Directory
} .EnumerateFiles(Directory.GetCurrentDirectory(), "*.fsproj", SearchOption.TopDirectoryOnly)
.FirstOrDefault();
/// <inheritdoc /> }
public void Dispose()
{ if(String.IsNullOrWhiteSpace(filename)) {
if (!_leaveOpen) throw new ArgumentNullException(nameof(filename));
{ }
_stream?.Dispose();
} return File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
} }
}
private static FileStream OpenFile(string filename)
{
if (filename == null)
{
filename = Directory
.EnumerateFiles(Directory.GetCurrentDirectory(), "*.csproj", SearchOption.TopDirectoryOnly)
.FirstOrDefault();
}
if (filename == null)
{
filename = Directory
.EnumerateFiles(Directory.GetCurrentDirectory(), "*.fsproj", SearchOption.TopDirectoryOnly)
.FirstOrDefault();
}
if (string.IsNullOrWhiteSpace(filename))
throw new ArgumentNullException(nameof(filename));
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.Xml.Linq;
namespace Unosquare.Swan.Components
{ {
using System.Linq;
using System.Xml.Linq;
/// <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,144 +1,139 @@
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>
/// Represents logic providing several delay mechanisms.
/// </summary>
/// <example>
/// The following example shows how to implement delay mechanisms.
/// <code>
/// using Unosquare.Swan.Components;
///
/// public class Example
/// {
/// public static void Main()
/// {
/// // using the ThreadSleep strategy
/// using (var delay = new DelayProvider(DelayProvider.DelayStrategy.ThreadSleep))
/// {
/// // retrieve how much time was delayed
/// var time = delay.WaitOne();
/// }
/// }
/// }
/// </code>
/// </example>
public sealed class DelayProvider : IDisposable {
private readonly Object _syncRoot = new Object();
private readonly Stopwatch _delayStopwatch = new Stopwatch();
private Boolean _isDisposed;
private IWaitEvent _delayEvent;
/// <summary> /// <summary>
/// Represents logic providing several delay mechanisms. /// Initializes a new instance of the <see cref="DelayProvider"/> class.
/// </summary> /// </summary>
/// <example> /// <param name="strategy">The strategy.</param>
/// The following example shows how to implement delay mechanisms. public DelayProvider(DelayStrategy strategy = DelayStrategy.TaskDelay) => this.Strategy = strategy;
/// <code>
/// using Unosquare.Swan.Components; /// <summary>
/// /// Enumerates the different ways of providing delays.
/// public class Example /// </summary>
/// { public enum DelayStrategy {
/// public static void Main() /// <summary>
/// { /// Using the Thread.Sleep(15) mechanism.
/// // using the ThreadSleep strategy /// </summary>
/// using (var delay = new DelayProvider(DelayProvider.DelayStrategy.ThreadSleep)) ThreadSleep,
/// {
/// // retrieve how much time was delayed /// <summary>
/// var time = delay.WaitOne(); /// Using the Task.Delay(1).Wait mechanism.
/// } /// </summary>
/// } TaskDelay,
/// }
/// </code> /// <summary>
/// </example> /// Using a wait event that completes in a background ThreadPool thread.
public sealed class DelayProvider : IDisposable /// </summary>
{ ThreadPool,
private readonly object _syncRoot = new object(); }
private readonly Stopwatch _delayStopwatch = new Stopwatch();
/// <summary>
private bool _isDisposed; /// Gets the selected delay strategy.
private IWaitEvent _delayEvent; /// </summary>
public DelayStrategy Strategy {
/// <summary> get;
/// Initializes a new instance of the <see cref="DelayProvider"/> class. }
/// </summary>
/// <param name="strategy">The strategy.</param> /// <summary>
public DelayProvider(DelayStrategy strategy = DelayStrategy.TaskDelay) /// Creates the smallest possible, synchronous delay based on the selected strategy.
{ /// </summary>
Strategy = strategy; /// <returns>The elapsed time of the delay.</returns>
} public TimeSpan WaitOne() {
lock(this._syncRoot) {
/// <summary> if(this._isDisposed) {
/// Enumerates the different ways of providing delays. return TimeSpan.Zero;
/// </summary> }
public enum DelayStrategy
{ this._delayStopwatch.Restart();
/// <summary>
/// Using the Thread.Sleep(15) mechanism. switch(this.Strategy) {
/// </summary> case DelayStrategy.ThreadSleep:
ThreadSleep, DelaySleep();
break;
/// <summary> case DelayStrategy.TaskDelay:
/// Using the Task.Delay(1).Wait mechanism. DelayTask();
/// </summary> break;
TaskDelay, #if !NETSTANDARD1_3
case DelayStrategy.ThreadPool:
/// <summary> this.DelayThreadPool();
/// Using a wait event that completes in a background ThreadPool thread. break;
/// </summary>
ThreadPool,
}
/// <summary>
/// Gets the selected delay strategy.
/// </summary>
public DelayStrategy Strategy { get; }
/// <summary>
/// Creates the smallest possible, synchronous delay based on the selected strategy.
/// </summary>
/// <returns>The elapsed time of the delay.</returns>
public TimeSpan WaitOne()
{
lock (_syncRoot)
{
if (_isDisposed) return TimeSpan.Zero;
_delayStopwatch.Restart();
switch (Strategy)
{
case DelayStrategy.ThreadSleep:
DelaySleep();
break;
case DelayStrategy.TaskDelay:
DelayTask();
break;
#if !NETSTANDARD1_3
case DelayStrategy.ThreadPool:
DelayThreadPool();
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();
} }
}
#endregion
#endregion
#region Private Delay Mechanisms
#region Private Delay Mechanisms
private static void DelaySleep() => Thread.Sleep(15);
private static void DelaySleep() => Thread.Sleep(15);
private static void DelayTask() => Task.Delay(1).Wait();
private static void DelayTask() => Task.Delay(1).Wait();
#if !NETSTANDARD1_3
private void DelayThreadPool() #if !NETSTANDARD1_3
{ private void DelayThreadPool() {
if (_delayEvent == null) if(this._delayEvent == null) {
_delayEvent = WaitEventFactory.Create(isCompleted: true, useSlim: true); this._delayEvent = WaitEventFactory.Create(isCompleted: true, useSlim: true);
}
_delayEvent.Begin();
ThreadPool.QueueUserWorkItem((s) => this._delayEvent.Begin();
{ _ = ThreadPool.QueueUserWorkItem((s) => {
DelaySleep(); DelaySleep();
_delayEvent.Complete(); this._delayEvent.Complete();
}); });
_delayEvent.Wait(); this._delayEvent.Wait();
} }
#endif #endif
#endregion #endregion
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,114 +1,113 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections.Generic;
using System.Collections.Generic;
namespace Unosquare.Swan.Components {
/// <summary>
/// Resolution settings.
/// </summary>
public class DependencyContainerResolveOptions {
/// <summary> /// <summary>
/// Resolution settings. /// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found).
/// </summary> /// </summary>
public class DependencyContainerResolveOptions public static DependencyContainerResolveOptions Default { get; } = new DependencyContainerResolveOptions();
{
/// <summary>
/// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found).
/// </summary>
public static DependencyContainerResolveOptions Default { get; } = new DependencyContainerResolveOptions();
/// <summary>
/// Gets or sets the unregistered resolution action.
/// </summary>
/// <value>
/// The unregistered resolution action.
/// </value>
public DependencyContainerUnregisteredResolutionActions UnregisteredResolutionAction { get; set; } =
DependencyContainerUnregisteredResolutionActions.AttemptResolve;
/// <summary>
/// Gets or sets the named resolution failure action.
/// </summary>
/// <value>
/// The named resolution failure action.
/// </value>
public DependencyContainerNamedResolutionFailureActions NamedResolutionFailureAction { get; set; } =
DependencyContainerNamedResolutionFailureActions.Fail;
/// <summary>
/// Gets the constructor parameters.
/// </summary>
/// <value>
/// The constructor parameters.
/// </value>
public Dictionary<string, object> ConstructorParameters { get; } = new Dictionary<string, object>();
/// <summary>
/// Clones this instance.
/// </summary>
/// <returns></returns>
public DependencyContainerResolveOptions Clone() => new DependencyContainerResolveOptions
{
NamedResolutionFailureAction = NamedResolutionFailureAction,
UnregisteredResolutionAction = UnregisteredResolutionAction,
};
}
/// <summary> /// <summary>
/// Defines Resolution actions. /// Gets or sets the unregistered resolution action.
/// </summary> /// </summary>
public enum DependencyContainerUnregisteredResolutionActions /// <value>
{ /// The unregistered resolution action.
/// <summary> /// </value>
/// Attempt to resolve type, even if the type isn't registered. public DependencyContainerUnregisteredResolutionActions UnregisteredResolutionAction {
/// get; set;
/// Registered types/options will always take precedence. } =
/// </summary> DependencyContainerUnregisteredResolutionActions.AttemptResolve;
AttemptResolve,
/// <summary>
/// Fail resolution if type not explicitly registered
/// </summary>
Fail,
/// <summary>
/// Attempt to resolve unregistered type if requested type is generic
/// and no registration exists for the specific generic parameters used.
///
/// Registered types/options will always take precedence.
/// </summary>
GenericsOnly,
}
/// <summary> /// <summary>
/// Enumerates failure actions. /// Gets or sets the named resolution failure action.
/// </summary> /// </summary>
public enum DependencyContainerNamedResolutionFailureActions /// <value>
{ /// The named resolution failure action.
/// <summary> /// </value>
/// The attempt unnamed resolution public DependencyContainerNamedResolutionFailureActions NamedResolutionFailureAction {
/// </summary> get; set;
AttemptUnnamedResolution, } =
DependencyContainerNamedResolutionFailureActions.Fail;
/// <summary>
/// The fail
/// </summary>
Fail,
}
/// <summary> /// <summary>
/// Enumerates duplicate definition actions. /// Gets the constructor parameters.
/// </summary> /// </summary>
public enum DependencyContainerDuplicateImplementationActions /// <value>
{ /// The constructor parameters.
/// <summary> /// </value>
/// The register single public Dictionary<String, Object> ConstructorParameters { get; } = new Dictionary<String, Object>();
/// </summary>
RegisterSingle, /// <summary>
/// Clones this instance.
/// <summary> /// </summary>
/// The register multiple /// <returns></returns>
/// </summary> public DependencyContainerResolveOptions Clone() => new DependencyContainerResolveOptions {
RegisterMultiple, NamedResolutionFailureAction = NamedResolutionFailureAction,
UnregisteredResolutionAction = UnregisteredResolutionAction,
/// <summary> };
/// The fail }
/// </summary>
Fail, /// <summary>
} /// Defines Resolution actions.
/// </summary>
public enum DependencyContainerUnregisteredResolutionActions {
/// <summary>
/// Attempt to resolve type, even if the type isn't registered.
///
/// Registered types/options will always take precedence.
/// </summary>
AttemptResolve,
/// <summary>
/// Fail resolution if type not explicitly registered
/// </summary>
Fail,
/// <summary>
/// Attempt to resolve unregistered type if requested type is generic
/// and no registration exists for the specific generic parameters used.
///
/// Registered types/options will always take precedence.
/// </summary>
GenericsOnly,
}
/// <summary>
/// Enumerates failure actions.
/// </summary>
public enum DependencyContainerNamedResolutionFailureActions {
/// <summary>
/// The attempt unnamed resolution
/// </summary>
AttemptUnnamedResolution,
/// <summary>
/// The fail
/// </summary>
Fail,
}
/// <summary>
/// Enumerates duplicate definition actions.
/// </summary>
public enum DependencyContainerDuplicateImplementationActions {
/// <summary>
/// The register single
/// </summary>
RegisterSingle,
/// <summary>
/// The register multiple
/// </summary>
RegisterMultiple,
/// <summary>
/// The fail
/// </summary>
Fail,
}
} }

View File

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

View File

@ -1,466 +1,443 @@
// =============================================================================== // ===============================================================================
// TinyIoC - TinyMessenger // TinyIoC - TinyMessenger
// //
// A simple messenger/event aggregator. // A simple messenger/event aggregator.
// //
// https://github.com/grumpydev/TinyIoC/blob/master/src/TinyIoC/TinyMessenger.cs // https://github.com/grumpydev/TinyIoC/blob/master/src/TinyIoC/TinyMessenger.cs
// =============================================================================== // ===============================================================================
// Copyright © Steven Robbins. All rights reserved. // Copyright © Steven Robbins. All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// 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>
/// Represents a message subscription.
/// </summary>
public interface IMessageHubSubscription {
/// <summary> /// <summary>
/// Represents a message subscription. /// Token returned to the subscribed to reference this subscription.
/// </summary> /// </summary>
public interface IMessageHubSubscription MessageHubSubscriptionToken SubscriptionToken {
{ get;
/// <summary> }
/// Token returned to the subscribed to reference this subscription.
/// </summary>
MessageHubSubscriptionToken SubscriptionToken { get; }
/// <summary>
/// Whether delivery should be attempted.
/// </summary>
/// <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>
bool ShouldAttemptDelivery(IMessageHubMessage message);
/// <summary>
/// Deliver the message.
/// </summary>
/// <param name="message">Message to deliver.</param>
void Deliver(IMessageHubMessage message);
}
/// <summary> /// <summary>
/// Message proxy definition. /// Whether delivery should be attempted.
/// </summary>
/// <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>
Boolean ShouldAttemptDelivery(IMessageHubMessage message);
/// <summary>
/// Deliver the message.
/// </summary>
/// <param name="message">Message to deliver.</param>
void Deliver(IMessageHubMessage message);
}
/// <summary>
/// Message proxy definition.
///
/// A message proxy can be used to intercept/alter messages and/or
/// marshal delivery actions onto a particular thread.
/// </summary>
public interface IMessageHubProxy {
/// <summary>
/// Delivers the specified message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="subscription">The subscription.</param>
void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription);
}
/// <summary>
/// Default "pass through" proxy.
///
/// Does nothing other than deliver the message.
/// </summary>
public sealed class MessageHubDefaultProxy : IMessageHubProxy {
private MessageHubDefaultProxy() {
// placeholder
}
/// <summary>
/// Singleton instance of the proxy.
/// </summary>
public static MessageHubDefaultProxy Instance { get; } = new MessageHubDefaultProxy();
/// <summary>
/// Delivers the specified message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="subscription">The subscription.</param>
public void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription)
=> subscription.Deliver(message);
}
#endregion
#region Hub Interface
/// <summary>
/// Messenger hub responsible for taking subscriptions/publications and delivering of messages.
/// </summary>
public interface IMessageHub {
/// <summary>
/// Subscribe to a message type with the given destination and delivery action.
/// Messages will be delivered via the specified proxy.
/// ///
/// A message proxy can be used to intercept/alter messages and/or /// All messages of this type will be delivered.
/// marshal delivery actions onto a particular thread.
/// </summary> /// </summary>
public interface IMessageHubProxy /// <typeparam name="TMessage">Type of message.</typeparam>
{ /// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <summary> /// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param>
/// Delivers the specified message. /// <param name="proxy">Proxy to use when delivering the messages.</param>
/// </summary> /// <returns>MessageSubscription used to unsubscribing.</returns>
/// <param name="message">The message.</param> MessageHubSubscriptionToken Subscribe<TMessage>(
/// <param name="subscription">The subscription.</param> Action<TMessage> deliveryAction,
void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription); Boolean useStrongReferences,
} IMessageHubProxy proxy)
where TMessage : class, IMessageHubMessage;
/// <summary> /// <summary>
/// Default "pass through" proxy. /// Subscribe to a message type with the given destination and delivery action with the given filter.
/// Messages will be delivered via the specified proxy.
/// All references are held with WeakReferences
/// Only messages that "pass" the filter will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="messageFilter">The message filter.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>
/// MessageSubscription used to unsubscribing.
/// </returns>
MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
Func<TMessage, Boolean> messageFilter,
Boolean useStrongReferences,
IMessageHubProxy proxy)
where TMessage : class, IMessageHubMessage;
/// <summary>
/// Unsubscribe from a particular message type.
/// ///
/// Does nothing other than deliver the message. /// Does not throw an exception if the subscription is not found.
/// </summary> /// </summary>
public sealed class MessageHubDefaultProxy : IMessageHubProxy /// <typeparam name="TMessage">Type of message.</typeparam>
{ /// <param name="subscriptionToken">Subscription token received from Subscribe.</param>
private MessageHubDefaultProxy() void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
{ where TMessage : class, IMessageHubMessage;
// placeholder
}
/// <summary>
/// Singleton instance of the proxy.
/// </summary>
public static MessageHubDefaultProxy Instance { get; } = new MessageHubDefaultProxy();
/// <summary>
/// Delivers the specified message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="subscription">The subscription.</param>
public void Deliver(IMessageHubMessage message, IMessageHubSubscription subscription)
=> subscription.Deliver(message);
}
#endregion
#region Hub Interface
/// <summary> /// <summary>
/// Messenger hub responsible for taking subscriptions/publications and delivering of messages. /// Publish a message to any subscribers.
/// </summary> /// </summary>
public interface IMessageHub /// <typeparam name="TMessage">Type of message.</typeparam>
{ /// <param name="message">Message to deliver.</param>
/// <summary> void Publish<TMessage>(TMessage message)
/// Subscribe to a message type with the given destination and delivery action. where TMessage : class, IMessageHubMessage;
/// Messages will be delivered via the specified proxy.
/// /// <summary>
/// All messages of this type will be delivered. /// Publish a message to any subscribers asynchronously.
/// </summary> /// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam> /// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param> /// <param name="message">Message to deliver.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param> /// <returns>A task from Publish action.</returns>
/// <param name="proxy">Proxy to use when delivering the messages.</param> Task PublishAsync<TMessage>(TMessage message)
/// <returns>MessageSubscription used to unsubscribing.</returns> where TMessage : class, IMessageHubMessage;
MessageHubSubscriptionToken Subscribe<TMessage>( }
Action<TMessage> deliveryAction,
bool useStrongReferences, #endregion
IMessageHubProxy proxy)
where TMessage : class, IMessageHubMessage; #region Hub Implementation
/// <summary> /// <inheritdoc />
/// Subscribe to a message type with the given destination and delivery action with the given filter. /// <example>
/// Messages will be delivered via the specified proxy. /// The following code describes how to use a MessageHub. Both the
/// All references are held with WeakReferences /// subscription and the message sending are done in the same place but this is only for explanatory purposes.
/// Only messages that "pass" the filter will be delivered. /// <code>
/// </summary> /// class Example
/// <typeparam name="TMessage">Type of message.</typeparam> /// {
/// <param name="deliveryAction">Action to invoke when message is delivered.</param> /// using Unosquare.Swan;
/// <param name="messageFilter">The message filter.</param> /// using Unosquare.Swan.Components;
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param> ///
/// <param name="proxy">Proxy to use when delivering the messages.</param> /// static void Main()
/// <returns> /// {
/// MessageSubscription used to unsubscribing. /// // using DependencyContainer to create an instance of MessageHub
/// </returns> /// var messageHub = DependencyContainer
MessageHubSubscriptionToken Subscribe<TMessage>( /// .Current
Action<TMessage> deliveryAction, /// .Resolve&lt;IMessageHub&gt;() as MessageHub;
Func<TMessage, bool> messageFilter, ///
bool useStrongReferences, /// // create an instance of the publisher class
IMessageHubProxy proxy) /// // which has a string as its content
where TMessage : class, IMessageHubMessage; /// var message = new MessageHubGenericMessage&lt;string&gt;(new object(), "SWAN");
///
/// <summary> /// // subscribe to the publisher's event
/// Unsubscribe from a particular message type. /// // and just print out the content which is a string
/// /// // a token is returned which can be used to unsubscribe later on
/// Does not throw an exception if the subscription is not found. /// var token = messageHub
/// </summary> /// .Subscribe&lt;MessageHubGenericMessage&lt;string&gt;&gt;(m =&gt; m.Content.Info());
/// <typeparam name="TMessage">Type of message.</typeparam> ///
/// <param name="subscriptionToken">Subscription token received from Subscribe.</param> /// // publish the message described above which is
void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken) /// // the string 'SWAN'
where TMessage : class, IMessageHubMessage; /// messageHub.Publish(message);
///
/// <summary> /// // unsuscribe, we will no longer receive any messages
/// Publish a message to any subscribers. /// messageHub.Unsubscribe&lt;MessageHubGenericMessage&lt;string&gt;&gt;(token);
/// </summary> ///
/// <typeparam name="TMessage">Type of message.</typeparam> /// Terminal.Flush();
/// <param name="message">Message to deliver.</param> /// }
void Publish<TMessage>(TMessage message) ///
where TMessage : class, IMessageHubMessage; /// }
/// </code>
/// <summary> /// </example>
/// Publish a message to any subscribers asynchronously. public sealed class MessageHub : IMessageHub {
/// </summary> #region Private Types and Interfaces
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="message">Message to deliver.</param> private readonly Object _subscriptionsPadlock = new Object();
/// <returns>A task from Publish action.</returns>
Task PublishAsync<TMessage>(TMessage message) private readonly Dictionary<Type, List<SubscriptionItem>> _subscriptions =
where TMessage : class, IMessageHubMessage; new Dictionary<Type, List<SubscriptionItem>>();
}
private class WeakMessageSubscription<TMessage> : IMessageHubSubscription
where TMessage : class, IMessageHubMessage {
private readonly WeakReference _deliveryAction;
private readonly WeakReference _messageFilter;
/// <summary>
/// Initializes a new instance of the <see cref="WeakMessageSubscription{TMessage}" /> class.
/// </summary>
/// <param name="subscriptionToken">The subscription token.</param>
/// <param name="deliveryAction">The delivery action.</param>
/// <param name="messageFilter">The message filter.</param>
/// <exception cref="ArgumentNullException">subscriptionToken
/// or
/// deliveryAction
/// or
/// messageFilter.</exception>
public WeakMessageSubscription(
MessageHubSubscriptionToken subscriptionToken,
Action<TMessage> deliveryAction,
Func<TMessage, Boolean> messageFilter) {
this.SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
this._deliveryAction = new WeakReference(deliveryAction);
this._messageFilter = new WeakReference(messageFilter);
}
public MessageHubSubscriptionToken SubscriptionToken {
get;
}
public Boolean ShouldAttemptDelivery(IMessageHubMessage message) => this._deliveryAction.IsAlive && this._messageFilter.IsAlive &&
((Func<TMessage, Boolean>)this._messageFilter.Target).Invoke((TMessage)message);
public void Deliver(IMessageHubMessage message) {
if(this._deliveryAction.IsAlive) {
((Action<TMessage>)this._deliveryAction.Target).Invoke((TMessage)message);
}
}
}
private class StrongMessageSubscription<TMessage> : IMessageHubSubscription
where TMessage : class, IMessageHubMessage {
private readonly Action<TMessage> _deliveryAction;
private readonly Func<TMessage, Boolean> _messageFilter;
/// <summary>
/// Initializes a new instance of the <see cref="StrongMessageSubscription{TMessage}" /> class.
/// </summary>
/// <param name="subscriptionToken">The subscription token.</param>
/// <param name="deliveryAction">The delivery action.</param>
/// <param name="messageFilter">The message filter.</param>
/// <exception cref="ArgumentNullException">subscriptionToken
/// or
/// deliveryAction
/// or
/// messageFilter.</exception>
public StrongMessageSubscription(
MessageHubSubscriptionToken subscriptionToken,
Action<TMessage> deliveryAction,
Func<TMessage, Boolean> messageFilter) {
this.SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
this._deliveryAction = deliveryAction;
this._messageFilter = messageFilter;
}
public MessageHubSubscriptionToken SubscriptionToken {
get;
}
public Boolean ShouldAttemptDelivery(IMessageHubMessage message) => this._messageFilter.Invoke((TMessage)message);
public void Deliver(IMessageHubMessage message) => this._deliveryAction.Invoke((TMessage)message);
}
#endregion #endregion
#region Hub Implementation #region Subscription dictionary
private class SubscriptionItem {
public SubscriptionItem(IMessageHubProxy proxy, IMessageHubSubscription subscription) {
this.Proxy = proxy;
this.Subscription = subscription;
}
public IMessageHubProxy Proxy {
get;
}
public IMessageHubSubscription Subscription {
get;
}
}
#endregion
#region Public API
/// <summary>
/// Subscribe to a message type with the given destination and delivery action.
/// Messages will be delivered via the specified proxy.
///
/// All messages of this type will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction. </param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>MessageSubscription used to unsubscribing.</returns>
public MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
Boolean useStrongReferences = true,
IMessageHubProxy proxy = null)
where TMessage : class, IMessageHubMessage => this.Subscribe(deliveryAction, m => true, useStrongReferences, proxy);
/// <summary>
/// Subscribe to a message type with the given destination and delivery action with the given filter.
/// Messages will be delivered via the specified proxy.
/// All references are held with WeakReferences
/// Only messages that "pass" the filter will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="messageFilter">The message filter.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>
/// MessageSubscription used to unsubscribing.
/// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
public MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
Func<TMessage, Boolean> messageFilter,
Boolean useStrongReferences = true,
IMessageHubProxy proxy = null)
where TMessage : class, IMessageHubMessage {
if(deliveryAction == null) {
throw new ArgumentNullException(nameof(deliveryAction));
}
if(messageFilter == null) {
throw new ArgumentNullException(nameof(messageFilter));
}
lock(this._subscriptionsPadlock) {
if(!this._subscriptions.TryGetValue(typeof(TMessage), out List<SubscriptionItem> currentSubscriptions)) {
currentSubscriptions = new List<SubscriptionItem>();
this._subscriptions[typeof(TMessage)] = currentSubscriptions;
}
MessageHubSubscriptionToken subscriptionToken = new MessageHubSubscriptionToken(this, typeof(TMessage));
IMessageHubSubscription subscription = useStrongReferences
? new StrongMessageSubscription<TMessage>(
subscriptionToken,
deliveryAction,
messageFilter)
: (IMessageHubSubscription)new WeakMessageSubscription<TMessage>(
subscriptionToken,
deliveryAction,
messageFilter);
currentSubscriptions.Add(new SubscriptionItem(proxy ?? MessageHubDefaultProxy.Instance, subscription));
return subscriptionToken;
}
}
/// <inheritdoc /> /// <inheritdoc />
/// <example> public void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
/// The following code describes how to use a MessageHub. Both the where TMessage : class, IMessageHubMessage {
/// subscription and the message sending are done in the same place but this is only for explanatory purposes. if(subscriptionToken == null) {
/// <code> throw new ArgumentNullException(nameof(subscriptionToken));
/// class Example }
/// {
/// using Unosquare.Swan; lock(this._subscriptionsPadlock) {
/// using Unosquare.Swan.Components; if(!this._subscriptions.TryGetValue(typeof(TMessage), out List<SubscriptionItem> currentSubscriptions)) {
/// return;
/// static void Main() }
/// {
/// // using DependencyContainer to create an instance of MessageHub List<SubscriptionItem> currentlySubscribed = currentSubscriptions
/// var messageHub = DependencyContainer .Where(sub => ReferenceEquals(sub.Subscription.SubscriptionToken, subscriptionToken))
/// .Current .ToList();
/// .Resolve&lt;IMessageHub&gt;() as MessageHub;
/// currentlySubscribed.ForEach(sub => currentSubscriptions.Remove(sub));
/// // create an instance of the publisher class }
/// // which has a string as its content }
/// var message = new MessageHubGenericMessage&lt;string&gt;(new object(), "SWAN");
/// /// <summary>
/// // subscribe to the publisher's event /// Publish a message to any subscribers.
/// // and just print out the content which is a string /// </summary>
/// // a token is returned which can be used to unsubscribe later on /// <typeparam name="TMessage">Type of message.</typeparam>
/// var token = messageHub /// <param name="message">Message to deliver.</param>
/// .Subscribe&lt;MessageHubGenericMessage&lt;string&gt;&gt;(m =&gt; m.Content.Info()); public void Publish<TMessage>(TMessage message)
/// where TMessage : class, IMessageHubMessage {
/// // publish the message described above which is if(message == null) {
/// // the string 'SWAN' throw new ArgumentNullException(nameof(message));
/// messageHub.Publish(message); }
///
/// // unsuscribe, we will no longer receive any messages List<SubscriptionItem> currentlySubscribed;
/// messageHub.Unsubscribe&lt;MessageHubGenericMessage&lt;string&gt;&gt;(token); lock(this._subscriptionsPadlock) {
/// if(!this._subscriptions.TryGetValue(typeof(TMessage), out List<SubscriptionItem> currentSubscriptions)) {
/// Terminal.Flush(); return;
/// } }
///
/// } currentlySubscribed = currentSubscriptions
/// </code> .Where(sub => sub.Subscription.ShouldAttemptDelivery(message))
/// </example> .ToList();
public sealed class MessageHub : IMessageHub }
{
#region Private Types and Interfaces currentlySubscribed.ForEach(sub => {
try {
private readonly object _subscriptionsPadlock = new object(); sub.Proxy.Deliver(message, sub.Subscription);
} catch {
private readonly Dictionary<Type, List<SubscriptionItem>> _subscriptions = // Ignore any errors and carry on
new Dictionary<Type, List<SubscriptionItem>>(); }
});
private class WeakMessageSubscription<TMessage> : IMessageHubSubscription }
where TMessage : class, IMessageHubMessage
{ /// <summary>
private readonly WeakReference _deliveryAction; /// Publish a message to any subscribers asynchronously.
private readonly WeakReference _messageFilter; /// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <summary> /// <param name="message">Message to deliver.</param>
/// Initializes a new instance of the <see cref="WeakMessageSubscription{TMessage}" /> class. /// <returns>A task with the publish.</returns>
/// </summary> public Task PublishAsync<TMessage>(TMessage message)
/// <param name="subscriptionToken">The subscription token.</param> where TMessage : class, IMessageHubMessage => Task.Run(() => this.Publish(message));
/// <param name="deliveryAction">The delivery action.</param>
/// <param name="messageFilter">The message filter.</param>
/// <exception cref="ArgumentNullException">subscriptionToken
/// or
/// deliveryAction
/// or
/// messageFilter.</exception>
public WeakMessageSubscription(
MessageHubSubscriptionToken subscriptionToken,
Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter)
{
SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
_deliveryAction = new WeakReference(deliveryAction);
_messageFilter = new WeakReference(messageFilter);
}
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)
{
if (_deliveryAction.IsAlive)
{
((Action<TMessage>) _deliveryAction.Target).Invoke((TMessage) message);
}
}
}
private class StrongMessageSubscription<TMessage> : IMessageHubSubscription
where TMessage : class, IMessageHubMessage
{
private readonly Action<TMessage> _deliveryAction;
private readonly Func<TMessage, bool> _messageFilter;
/// <summary>
/// Initializes a new instance of the <see cref="StrongMessageSubscription{TMessage}" /> class.
/// </summary>
/// <param name="subscriptionToken">The subscription token.</param>
/// <param name="deliveryAction">The delivery action.</param>
/// <param name="messageFilter">The message filter.</param>
/// <exception cref="ArgumentNullException">subscriptionToken
/// or
/// deliveryAction
/// or
/// messageFilter.</exception>
public StrongMessageSubscription(
MessageHubSubscriptionToken subscriptionToken,
Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter)
{
SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
_deliveryAction = deliveryAction;
_messageFilter = messageFilter;
}
public MessageHubSubscriptionToken SubscriptionToken { get; }
public bool ShouldAttemptDelivery(IMessageHubMessage message) => _messageFilter.Invoke((TMessage) message);
public void Deliver(IMessageHubMessage message) => _deliveryAction.Invoke((TMessage) message);
}
#endregion
#region Subscription dictionary
private class SubscriptionItem
{
public SubscriptionItem(IMessageHubProxy proxy, IMessageHubSubscription subscription)
{
Proxy = proxy;
Subscription = subscription;
}
public IMessageHubProxy Proxy { get; }
public IMessageHubSubscription Subscription { get; }
}
#endregion
#region Public API
/// <summary>
/// Subscribe to a message type with the given destination and delivery action.
/// Messages will be delivered via the specified proxy.
///
/// All messages of this type will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction. </param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>MessageSubscription used to unsubscribing.</returns>
public MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
bool useStrongReferences = true,
IMessageHubProxy proxy = null)
where TMessage : class, IMessageHubMessage
{
return Subscribe(deliveryAction, m => true, useStrongReferences, proxy);
}
/// <summary>
/// Subscribe to a message type with the given destination and delivery action with the given filter.
/// Messages will be delivered via the specified proxy.
/// All references are held with WeakReferences
/// Only messages that "pass" the filter will be delivered.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="deliveryAction">Action to invoke when message is delivered.</param>
/// <param name="messageFilter">The message filter.</param>
/// <param name="useStrongReferences">Use strong references to destination and deliveryAction.</param>
/// <param name="proxy">Proxy to use when delivering the messages.</param>
/// <returns>
/// MessageSubscription used to unsubscribing.
/// </returns>
public MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter,
bool useStrongReferences = true,
IMessageHubProxy proxy = null)
where TMessage : class, IMessageHubMessage
{
if (deliveryAction == null)
throw new ArgumentNullException(nameof(deliveryAction));
if (messageFilter == null)
throw new ArgumentNullException(nameof(messageFilter));
lock (_subscriptionsPadlock)
{
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
{
currentSubscriptions = new List<SubscriptionItem>();
_subscriptions[typeof(TMessage)] = currentSubscriptions;
}
var subscriptionToken = new MessageHubSubscriptionToken(this, typeof(TMessage));
IMessageHubSubscription subscription;
if (useStrongReferences)
{
subscription = new StrongMessageSubscription<TMessage>(
subscriptionToken,
deliveryAction,
messageFilter);
}
else
{
subscription = new WeakMessageSubscription<TMessage>(
subscriptionToken,
deliveryAction,
messageFilter);
}
currentSubscriptions.Add(new SubscriptionItem(proxy ?? MessageHubDefaultProxy.Instance, subscription));
return subscriptionToken;
}
}
/// <inheritdoc />
public void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
where TMessage : class, IMessageHubMessage
{
if (subscriptionToken == null)
throw new ArgumentNullException(nameof(subscriptionToken));
lock (_subscriptionsPadlock)
{
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
return;
var currentlySubscribed = currentSubscriptions
.Where(sub => ReferenceEquals(sub.Subscription.SubscriptionToken, subscriptionToken))
.ToList();
currentlySubscribed.ForEach(sub => currentSubscriptions.Remove(sub));
}
}
/// <summary>
/// Publish a message to any subscribers.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="message">Message to deliver.</param>
public void Publish<TMessage>(TMessage message)
where TMessage : class, IMessageHubMessage
{
if (message == null)
throw new ArgumentNullException(nameof(message));
List<SubscriptionItem> currentlySubscribed;
lock (_subscriptionsPadlock)
{
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
return;
currentlySubscribed = currentSubscriptions
.Where(sub => sub.Subscription.ShouldAttemptDelivery(message))
.ToList();
}
currentlySubscribed.ForEach(sub =>
{
try
{
sub.Proxy.Deliver(message, sub.Subscription);
}
catch
{
// Ignore any errors and carry on
}
});
}
/// <summary>
/// Publish a message to any subscribers asynchronously.
/// </summary>
/// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="message">Message to deliver.</param>
/// <returns>A task with the publish.</returns>
public Task PublishAsync<TMessage>(TMessage message)
where TMessage : class, IMessageHubMessage
{
return Task.Run(() => Publish(message));
}
#endregion
}
#endregion #endregion
}
#endregion
} }

View File

@ -1,59 +1,55 @@
namespace Unosquare.Swan.Components using System;
{
using System; namespace Unosquare.Swan.Components {
/// <summary>
/// Base class for messages that provides weak reference storage of the sender.
/// </summary>
public abstract class MessageHubMessageBase
: IMessageHubMessage {
/// <summary> /// <summary>
/// Base class for messages that provides weak reference storage of the sender. /// 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.
/// </summary> /// </summary>
public abstract class MessageHubMessageBase private readonly WeakReference _sender;
: IMessageHubMessage
{
/// <summary>
/// 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.
/// </summary>
private readonly WeakReference _sender;
/// <summary>
/// Initializes a new instance of the <see cref="MessageHubMessageBase"/> class.
/// </summary>
/// <param name="sender">The sender.</param>
/// <exception cref="System.ArgumentNullException">sender.</exception>
protected MessageHubMessageBase(object sender)
{
if (sender == null)
throw new ArgumentNullException(nameof(sender));
_sender = new WeakReference(sender);
}
/// <summary>
/// The sender of the message, or null if not supported by the message implementation.
/// </summary>
public object Sender => _sender?.Target;
}
/// <summary> /// <summary>
/// Generic message with user specified content. /// Initializes a new instance of the <see cref="MessageHubMessageBase"/> class.
/// </summary> /// </summary>
/// <typeparam name="TContent">Content type to store.</typeparam> /// <param name="sender">The sender.</param>
public class MessageHubGenericMessage<TContent> /// <exception cref="System.ArgumentNullException">sender.</exception>
: MessageHubMessageBase protected MessageHubMessageBase(Object sender) {
{ if(sender == null) {
/// <summary> throw new ArgumentNullException(nameof(sender));
/// Initializes a new instance of the <see cref="MessageHubGenericMessage{TContent}"/> class. }
/// </summary>
/// <param name="sender">The sender.</param> this._sender = new WeakReference(sender);
/// <param name="content">The content.</param> }
public MessageHubGenericMessage(object sender, TContent content)
: base(sender) /// <summary>
{ /// The sender of the message, or null if not supported by the message implementation.
Content = content; /// </summary>
} public Object Sender => this._sender?.Target;
}
/// <summary>
/// Contents of the message. /// <summary>
/// </summary> /// Generic message with user specified content.
public TContent Content { get; protected set; } /// </summary>
} /// <typeparam name="TContent">Content type to store.</typeparam>
public class MessageHubGenericMessage<TContent>
: MessageHubMessageBase {
/// <summary>
/// Initializes a new instance of the <see cref="MessageHubGenericMessage{TContent}"/> class.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="content">The content.</param>
public MessageHubGenericMessage(Object sender, TContent content)
: base(sender) => this.Content = content;
/// <summary>
/// Contents of the message.
/// </summary>
public TContent Content {
get; protected set;
}
}
} }

View File

@ -1,54 +1,49 @@
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>
/// Represents an active subscription to a message.
/// </summary>
public sealed class MessageHubSubscriptionToken
: IDisposable {
private readonly WeakReference _hub;
private readonly Type _messageType;
/// <summary> /// <summary>
/// Represents an active subscription to a message. /// Initializes a new instance of the <see cref="MessageHubSubscriptionToken"/> class.
/// </summary> /// </summary>
public sealed class MessageHubSubscriptionToken /// <param name="hub">The hub.</param>
: IDisposable /// <param name="messageType">Type of the message.</param>
{ /// <exception cref="System.ArgumentNullException">hub.</exception>
private readonly WeakReference _hub; /// <exception cref="System.ArgumentOutOfRangeException">messageType.</exception>
private readonly Type _messageType; public MessageHubSubscriptionToken(IMessageHub hub, Type messageType) {
if(hub == null) {
/// <summary> throw new ArgumentNullException(nameof(hub));
/// Initializes a new instance of the <see cref="MessageHubSubscriptionToken"/> class. }
/// </summary>
/// <param name="hub">The hub.</param> if(!typeof(IMessageHubMessage).IsAssignableFrom(messageType)) {
/// <param name="messageType">Type of the message.</param> throw new ArgumentOutOfRangeException(nameof(messageType));
/// <exception cref="System.ArgumentNullException">hub.</exception> }
/// <exception cref="System.ArgumentOutOfRangeException">messageType.</exception>
public MessageHubSubscriptionToken(IMessageHub hub, Type messageType) this._hub = new WeakReference(hub);
{ this._messageType = messageType;
if (hub == null) }
{
throw new ArgumentNullException(nameof(hub)); /// <inheritdoc />
} public void Dispose() {
if(this._hub.IsAlive && this._hub.Target is IMessageHub hub) {
if (!typeof(IMessageHubMessage).IsAssignableFrom(messageType)) System.Reflection.MethodInfo unsubscribeMethod = typeof(IMessageHub).GetMethod(nameof(IMessageHub.Unsubscribe),
{ new[] { typeof(MessageHubSubscriptionToken) });
throw new ArgumentOutOfRangeException(nameof(messageType)); unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(this._messageType);
} _ = unsubscribeMethod.Invoke(hub, new Object[] { this });
}
_hub = new WeakReference(hub);
_messageType = messageType; GC.SuppressFinalize(this);
} }
}
/// <inheritdoc />
public void Dispose()
{
if (_hub.IsAlive && _hub.Target is IMessageHub hub)
{
var unsubscribeMethod = typeof(IMessageHub).GetMethod(nameof(IMessageHub.Unsubscribe),
new[] {typeof(MessageHubSubscriptionToken)});
unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(_messageType);
unsubscribeMethod.Invoke(hub, new object[] {this});
}
GC.SuppressFinalize(this);
}
}
} }

View File

@ -1,424 +1,390 @@
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>
/// Represents an abstract class for Object Factory.
/// </summary>
public abstract class ObjectFactoryBase {
/// <summary> /// <summary>
/// Represents an abstract class for Object Factory. /// Whether to assume this factory successfully constructs its objects
///
/// Generally set to true for delegate style factories as CanResolve cannot delve
/// into the delegates they contain.
/// </summary> /// </summary>
public abstract class ObjectFactoryBase public virtual Boolean AssumeConstruction => false;
{
/// <summary>
/// Whether to assume this factory successfully constructs its objects
///
/// Generally set to true for delegate style factories as CanResolve cannot delve
/// into the delegates they contain.
/// </summary>
public virtual bool AssumeConstruction => false;
/// <summary>
/// The type the factory instantiates.
/// </summary>
public abstract Type CreatesType { get; }
/// <summary>
/// Constructor to use, if specified.
/// </summary>
public ConstructorInfo Constructor { get; private set; }
/// <summary>
/// Gets the singleton variant.
/// </summary>
/// <value>
/// The singleton variant.
/// </value>
/// <exception cref="DependencyContainerRegistrationException">singleton.</exception>
public virtual ObjectFactoryBase SingletonVariant =>
throw new DependencyContainerRegistrationException(GetType(), "singleton");
/// <summary>
/// Gets the multi instance variant.
/// </summary>
/// <value>
/// The multi instance variant.
/// </value>
/// <exception cref="DependencyContainerRegistrationException">multi-instance.</exception>
public virtual ObjectFactoryBase MultiInstanceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "multi-instance");
/// <summary>
/// Gets the strong reference variant.
/// </summary>
/// <value>
/// The strong reference variant.
/// </value>
/// <exception cref="DependencyContainerRegistrationException">strong reference.</exception>
public virtual ObjectFactoryBase StrongReferenceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "strong reference");
/// <summary>
/// Gets the weak reference variant.
/// </summary>
/// <value>
/// The weak reference variant.
/// </value>
/// <exception cref="DependencyContainerRegistrationException">weak reference.</exception>
public virtual ObjectFactoryBase WeakReferenceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "weak reference");
/// <summary>
/// Create the type.
/// </summary>
/// <param name="requestedType">Type user requested to be resolved.</param>
/// <param name="container">Container that requested the creation.</param>
/// <param name="options">The options.</param>
/// <returns> Instance of type. </returns>
public abstract object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options);
/// <summary>
/// Gets the factory for child container.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="parent">The parent.</param>
/// <param name="child">The child.</param>
/// <returns></returns>
public virtual ObjectFactoryBase GetFactoryForChildContainer(
Type type,
DependencyContainer parent,
DependencyContainer child)
{
return this;
}
}
/// <inheritdoc />
/// <summary> /// <summary>
/// IObjectFactory that creates new instances of types for each resolution. /// The type the factory instantiates.
/// </summary> /// </summary>
internal class MultiInstanceFactory : ObjectFactoryBase public abstract Type CreatesType {
{ get;
private readonly Type _registerType; }
private readonly Type _registerImplementation;
public MultiInstanceFactory(Type registerType, Type registerImplementation)
{
if (registerImplementation.IsAbstract() || registerImplementation.IsInterface())
{
throw new DependencyContainerRegistrationException(registerImplementation,
"MultiInstanceFactory",
true);
}
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
{
throw new DependencyContainerRegistrationException(registerImplementation,
"MultiInstanceFactory",
true);
}
_registerType = registerType;
_registerImplementation = registerImplementation;
}
public override Type CreatesType => _registerImplementation;
public override ObjectFactoryBase SingletonVariant =>
new SingletonFactory(_registerType, _registerImplementation);
public override ObjectFactoryBase MultiInstanceVariant => this;
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
try
{
return container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options);
}
catch (DependencyContainerResolutionException ex)
{
throw new DependencyContainerResolutionException(_registerType, ex);
}
}
}
/// <inheritdoc />
/// <summary> /// <summary>
/// IObjectFactory that invokes a specified delegate to construct the object. /// Constructor to use, if specified.
/// </summary> /// </summary>
internal class DelegateFactory : ObjectFactoryBase public ConstructorInfo Constructor {
{ get; private set;
private readonly Type _registerType; }
private readonly Func<DependencyContainer, Dictionary<string, object>, object> _factory;
public DelegateFactory(
Type registerType,
Func<DependencyContainer,
Dictionary<string, object>, object> factory)
{
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
_registerType = registerType;
}
public override bool AssumeConstruction => true;
public override Type CreatesType => _registerType;
public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(_registerType, _factory);
public override ObjectFactoryBase StrongReferenceVariant => this;
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
try
{
return _factory.Invoke(container, options.ConstructorParameters);
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(_registerType, ex);
}
}
}
/// <inheritdoc />
/// <summary> /// <summary>
/// IObjectFactory that invokes a specified delegate to construct the object /// Gets the singleton variant.
/// Holds the delegate using a weak reference.
/// </summary> /// </summary>
internal class WeakDelegateFactory : ObjectFactoryBase /// <value>
{ /// The singleton variant.
private readonly Type _registerType; /// </value>
/// <exception cref="DependencyContainerRegistrationException">singleton.</exception>
private readonly WeakReference _factory; public virtual ObjectFactoryBase SingletonVariant =>
throw new DependencyContainerRegistrationException(this.GetType(), "singleton");
public WeakDelegateFactory(Type registerType,
Func<DependencyContainer, Dictionary<string, object>, object> factory)
{
if (factory == null)
throw new ArgumentNullException(nameof(factory));
_factory = new WeakReference(factory);
_registerType = registerType;
}
public override bool AssumeConstruction => true;
public override Type CreatesType => _registerType;
public override ObjectFactoryBase StrongReferenceVariant
{
get
{
if (!(_factory.Target is Func<DependencyContainer, Dictionary<string, object>, object> factory))
throw new DependencyContainerWeakReferenceException(_registerType);
return new DelegateFactory(_registerType, factory);
}
}
public override ObjectFactoryBase WeakReferenceVariant => this;
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
if (!(_factory.Target is Func<DependencyContainer, Dictionary<string, object>, object> factory))
throw new DependencyContainerWeakReferenceException(_registerType);
try
{
return factory.Invoke(container, options.ConstructorParameters);
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(_registerType, ex);
}
}
}
/// <summary> /// <summary>
/// Stores an particular instance to return for a type. /// Gets the multi instance variant.
/// </summary> /// </summary>
internal class InstanceFactory : ObjectFactoryBase, IDisposable /// <value>
{ /// The multi instance variant.
private readonly Type _registerType; /// </value>
private readonly Type _registerImplementation; /// <exception cref="DependencyContainerRegistrationException">multi-instance.</exception>
private readonly object _instance; public virtual ObjectFactoryBase MultiInstanceVariant =>
throw new DependencyContainerRegistrationException(this.GetType(), "multi-instance");
public InstanceFactory(Type registerType, Type registerImplementation, object instance)
{
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true);
_registerType = registerType;
_registerImplementation = registerImplementation;
_instance = instance;
}
public override bool AssumeConstruction => true;
public override Type CreatesType => _registerImplementation;
public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(_registerType, _registerImplementation);
public override ObjectFactoryBase WeakReferenceVariant =>
new WeakInstanceFactory(_registerType, _registerImplementation, _instance);
public override ObjectFactoryBase StrongReferenceVariant => this;
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
return _instance;
}
public void Dispose()
{
var disposable = _instance as IDisposable;
disposable?.Dispose();
}
}
/// <summary> /// <summary>
/// Stores the instance with a weak reference. /// Gets the strong reference variant.
/// </summary> /// </summary>
internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable /// <value>
{ /// The strong reference variant.
private readonly Type _registerType; /// </value>
private readonly Type _registerImplementation; /// <exception cref="DependencyContainerRegistrationException">strong reference.</exception>
private readonly WeakReference _instance; public virtual ObjectFactoryBase StrongReferenceVariant =>
throw new DependencyContainerRegistrationException(this.GetType(), "strong reference");
public WeakInstanceFactory(Type registerType, Type registerImplementation, object instance)
{
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
{
throw new DependencyContainerRegistrationException(
registerImplementation,
"WeakInstanceFactory",
true);
}
_registerType = registerType;
_registerImplementation = registerImplementation;
_instance = new WeakReference(instance);
}
public override Type CreatesType => _registerImplementation;
public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(_registerType, _registerImplementation);
public override ObjectFactoryBase WeakReferenceVariant => this;
public override ObjectFactoryBase StrongReferenceVariant
{
get
{
var instance = _instance.Target;
if (instance == null)
throw new DependencyContainerWeakReferenceException(_registerType);
return new InstanceFactory(_registerType, _registerImplementation, instance);
}
}
public override object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options)
{
var instance = _instance.Target;
if (instance == null)
throw new DependencyContainerWeakReferenceException(_registerType);
return instance;
}
public void Dispose() => (_instance.Target as IDisposable)?.Dispose();
}
/// <summary> /// <summary>
/// A factory that lazy instantiates a type and always returns the same instance. /// Gets the weak reference variant.
/// </summary> /// </summary>
internal class SingletonFactory : ObjectFactoryBase, IDisposable /// <value>
{ /// The weak reference variant.
private readonly Type _registerType; /// </value>
private readonly Type _registerImplementation; /// <exception cref="DependencyContainerRegistrationException">weak reference.</exception>
private readonly object _singletonLock = new object(); public virtual ObjectFactoryBase WeakReferenceVariant =>
private object _current; throw new DependencyContainerRegistrationException(this.GetType(), "weak reference");
public SingletonFactory(Type registerType, Type registerImplementation) /// <summary>
{ /// Create the type.
if (registerImplementation.IsAbstract() || registerImplementation.IsInterface()) /// </summary>
{ /// <param name="requestedType">Type user requested to be resolved.</param>
throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true); /// <param name="container">Container that requested the creation.</param>
} /// <param name="options">The options.</param>
/// <returns> Instance of type. </returns>
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) public abstract Object GetObject(
{ Type requestedType,
throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true); DependencyContainer container,
} DependencyContainerResolveOptions options);
_registerType = registerType; /// <summary>
_registerImplementation = registerImplementation; /// Gets the factory for child container.
} /// </summary>
/// <param name="type">The type.</param>
public override Type CreatesType => _registerImplementation; /// <param name="parent">The parent.</param>
/// <param name="child">The child.</param>
public override ObjectFactoryBase SingletonVariant => this; /// <returns></returns>
public virtual ObjectFactoryBase GetFactoryForChildContainer(
public override ObjectFactoryBase MultiInstanceVariant => Type type,
new MultiInstanceFactory(_registerType, _registerImplementation); DependencyContainer parent,
DependencyContainer child) => this;
public override object GetObject( }
Type requestedType,
DependencyContainer container, /// <inheritdoc />
DependencyContainerResolveOptions options) /// <summary>
{ /// IObjectFactory that creates new instances of types for each resolution.
if (options.ConstructorParameters.Count != 0) /// </summary>
throw new ArgumentException("Cannot specify parameters for singleton types"); internal class MultiInstanceFactory : ObjectFactoryBase {
private readonly Type _registerType;
lock (_singletonLock) private readonly Type _registerImplementation;
{
if (_current == null) public MultiInstanceFactory(Type registerType, Type registerImplementation) {
_current = container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options); if(registerImplementation.IsAbstract() || registerImplementation.IsInterface()) {
} throw new DependencyContainerRegistrationException(registerImplementation,
"MultiInstanceFactory",
return _current; true);
} }
public override ObjectFactoryBase GetFactoryForChildContainer( if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
Type type, throw new DependencyContainerRegistrationException(registerImplementation,
DependencyContainer parent, "MultiInstanceFactory",
DependencyContainer child) true);
{ }
// 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 this._registerType = registerType;
// the type before the child container does. this._registerImplementation = registerImplementation;
GetObject(type, parent, DependencyContainerResolveOptions.Default); }
return this;
} public override Type CreatesType => this._registerImplementation;
public void Dispose() => (_current as IDisposable)?.Dispose(); public override ObjectFactoryBase SingletonVariant =>
} new SingletonFactory(this._registerType, this._registerImplementation);
public override ObjectFactoryBase MultiInstanceVariant => this;
public override Object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options) {
try {
return container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
} catch(DependencyContainerResolutionException ex) {
throw new DependencyContainerResolutionException(this._registerType, ex);
}
}
}
/// <inheritdoc />
/// <summary>
/// IObjectFactory that invokes a specified delegate to construct the object.
/// </summary>
internal class DelegateFactory : ObjectFactoryBase {
private readonly Type _registerType;
private readonly Func<DependencyContainer, Dictionary<String, Object>, Object> _factory;
public DelegateFactory(
Type registerType,
Func<DependencyContainer,
Dictionary<String, Object>, Object> factory) {
this._factory = factory ?? throw new ArgumentNullException(nameof(factory));
this._registerType = registerType;
}
public override Boolean AssumeConstruction => true;
public override Type CreatesType => this._registerType;
public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(this._registerType, this._factory);
public override ObjectFactoryBase StrongReferenceVariant => this;
public override Object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options) {
try {
return this._factory.Invoke(container, options.ConstructorParameters);
} catch(Exception ex) {
throw new DependencyContainerResolutionException(this._registerType, ex);
}
}
}
/// <inheritdoc />
/// <summary>
/// IObjectFactory that invokes a specified delegate to construct the object
/// Holds the delegate using a weak reference.
/// </summary>
internal class WeakDelegateFactory : ObjectFactoryBase {
private readonly Type _registerType;
private readonly WeakReference _factory;
public WeakDelegateFactory(Type registerType,
Func<DependencyContainer, Dictionary<String, Object>, Object> factory) {
if(factory == null) {
throw new ArgumentNullException(nameof(factory));
}
this._factory = new WeakReference(factory);
this._registerType = registerType;
}
public override Boolean AssumeConstruction => true;
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 Object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options) {
if(!(this._factory.Target is Func<DependencyContainer, Dictionary<global::System.String, global::System.Object>, global::System.Object> factory)) {
throw new DependencyContainerWeakReferenceException(this._registerType);
}
try {
return factory.Invoke(container, options.ConstructorParameters);
} catch(Exception ex) {
throw new DependencyContainerResolutionException(this._registerType, ex);
}
}
}
/// <summary>
/// Stores an particular instance to return for a type.
/// </summary>
internal class InstanceFactory : ObjectFactoryBase, IDisposable {
private readonly Type _registerType;
private readonly Type _registerImplementation;
private readonly Object _instance;
public InstanceFactory(Type registerType, Type registerImplementation, Object instance) {
if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true);
}
this._registerType = registerType;
this._registerImplementation = registerImplementation;
this._instance = instance;
}
public override Boolean AssumeConstruction => true;
public override Type CreatesType => this._registerImplementation;
public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(this._registerType, this._registerImplementation);
public override ObjectFactoryBase WeakReferenceVariant =>
new WeakInstanceFactory(this._registerType, this._registerImplementation, this._instance);
public override ObjectFactoryBase StrongReferenceVariant => this;
public override Object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options) => this._instance;
public void Dispose() {
IDisposable disposable = this._instance as IDisposable;
disposable?.Dispose();
}
}
/// <summary>
/// Stores the instance with a weak reference.
/// </summary>
internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable {
private readonly Type _registerType;
private readonly Type _registerImplementation;
private readonly WeakReference _instance;
public WeakInstanceFactory(Type registerType, Type registerImplementation, Object instance) {
if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
throw new DependencyContainerRegistrationException(
registerImplementation,
"WeakInstanceFactory",
true);
}
this._registerType = registerType;
this._registerImplementation = registerImplementation;
this._instance = new WeakReference(instance);
}
public override Type CreatesType => this._registerImplementation;
public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(this._registerType, this._registerImplementation);
public override ObjectFactoryBase WeakReferenceVariant => this;
public override ObjectFactoryBase StrongReferenceVariant {
get {
Object instance = this._instance.Target;
if(instance == null) {
throw new DependencyContainerWeakReferenceException(this._registerType);
}
return new InstanceFactory(this._registerType, this._registerImplementation, instance);
}
}
public override Object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options) {
Object instance = this._instance.Target;
if(instance == null) {
throw new DependencyContainerWeakReferenceException(this._registerType);
}
return instance;
}
public void Dispose() => (this._instance.Target as IDisposable)?.Dispose();
}
/// <summary>
/// A factory that lazy instantiates a type and always returns the same instance.
/// </summary>
internal class SingletonFactory : ObjectFactoryBase, IDisposable {
private readonly Type _registerType;
private readonly Type _registerImplementation;
private readonly Object _singletonLock = new Object();
private Object _current;
public SingletonFactory(Type registerType, Type registerImplementation) {
if(registerImplementation.IsAbstract() || registerImplementation.IsInterface()) {
throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
}
if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
}
this._registerType = registerType;
this._registerImplementation = registerImplementation;
}
public override Type CreatesType => this._registerImplementation;
public override ObjectFactoryBase SingletonVariant => this;
public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(this._registerType, this._registerImplementation);
public override Object GetObject(
Type requestedType,
DependencyContainer container,
DependencyContainerResolveOptions options) {
if(options.ConstructorParameters.Count != 0) {
throw new ArgumentException("Cannot specify parameters for singleton types");
}
lock(this._singletonLock) {
if(this._current == null) {
this._current = container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
}
}
return this._current;
}
public override ObjectFactoryBase GetFactoryForChildContainer(
Type type,
DependencyContainer parent,
DependencyContainer child) {
// 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
// the type before the child container does.
_ = this.GetObject(type, parent, DependencyContainerResolveOptions.Default);
return this;
}
public void Dispose() => (this._current as IDisposable)?.Dispose();
}
} }

View File

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

View File

@ -1,474 +1,447 @@
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>
/// Provides methods to help create external processes, and efficiently capture the
/// standard error and standard output streams.
/// </summary>
public static class ProcessRunner {
/// <summary> /// <summary>
/// Provides methods to help create external processes, and efficiently capture the /// Defines a delegate to handle binary data reception from the standard
/// standard error and standard output streams. /// output or standard error streams from a process.
/// </summary> /// </summary>
public static class ProcessRunner /// <param name="processData">The process data.</param>
{ /// <param name="process">The process.</param>
/// <summary> public delegate void ProcessDataReceivedCallback(Byte[] processData, Process process);
/// Defines a delegate to handle binary data reception from the standard
/// output or standard error streams from a process. /// <summary>
/// </summary> /// Runs the process asynchronously and if the exit code is 0,
/// <param name="processData">The process data.</param> /// returns all of the standard output text. If the exit code is something other than 0
/// <param name="process">The process.</param> /// it returns the contents of standard error.
public delegate void ProcessDataReceivedCallback(byte[] processData, Process process); /// This method is meant to be used for programs that output a relatively small amount of text.
/// </summary>
/// <summary> /// <param name="filename">The filename.</param>
/// Runs the process asynchronously and if the exit code is 0, /// <param name="ct">The cancellation token.</param>
/// returns all of the standard output text. If the exit code is something other than 0 /// <returns>The type of the result produced by this Task.</returns>
/// it returns the contents of standard error. public static Task<String> GetProcessOutputAsync(String filename, CancellationToken ct = default) =>
/// This method is meant to be used for programs that output a relatively small amount of text. GetProcessOutputAsync(filename, String.Empty, ct);
/// </summary>
/// <param name="filename">The filename.</param> /// <summary>
/// <param name="ct">The cancellation token.</param> /// Runs the process asynchronously and if the exit code is 0,
/// <returns>The type of the result produced by this Task.</returns> /// returns all of the standard output text. If the exit code is something other than 0
public static Task<string> GetProcessOutputAsync(string filename, CancellationToken ct = default) => /// it returns the contents of standard error.
GetProcessOutputAsync(filename, string.Empty, ct); /// This method is meant to be used for programs that output a relatively small amount of text.
/// </summary>
/// <summary> /// <param name="filename">The filename.</param>
/// Runs the process asynchronously and if the exit code is 0, /// <param name="arguments">The arguments.</param>
/// returns all of the standard output text. If the exit code is something other than 0 /// <param name="ct">The cancellation token.</param>
/// it returns the contents of standard error. /// <returns>The type of the result produced by this Task.</returns>
/// This method is meant to be used for programs that output a relatively small amount of text. /// <example>
/// </summary> /// The following code explains how to run an external process using the
/// <param name="filename">The filename.</param> /// <see cref="GetProcessOutputAsync(String, String, CancellationToken)"/> method.
/// <param name="arguments">The arguments.</param> /// <code>
/// <param name="ct">The cancellation token.</param> /// class Example
/// <returns>The type of the result produced by this Task.</returns> /// {
/// <example> /// using System.Threading.Tasks;
/// The following code explains how to run an external process using the /// using Unosquare.Swan.Components;
/// <see cref="GetProcessOutputAsync(string, string, CancellationToken)"/> method. ///
/// <code> /// static async Task Main()
/// class Example /// {
/// { /// // execute a process and save its output
/// using System.Threading.Tasks; /// var data = await ProcessRunner.
/// using Unosquare.Swan.Components; /// GetProcessOutputAsync("dotnet", "--help");
/// ///
/// static async Task Main() /// // print the output
/// { /// data.WriteLine();
/// // execute a process and save its output /// }
/// var data = await ProcessRunner. /// }
/// GetProcessOutputAsync("dotnet", "--help"); /// </code>
/// /// </example>
/// // print the output public static async Task<String> GetProcessOutputAsync(
/// data.WriteLine(); String filename,
/// } String arguments,
/// } CancellationToken ct = default) {
/// </code> ProcessResult result = await GetProcessResultAsync(filename, arguments, ct).ConfigureAwait(false);
/// </example> return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
public static async Task<string> GetProcessOutputAsync( }
string filename,
string arguments, /// <summary>
CancellationToken ct = default) /// Gets the process output asynchronous.
{ /// </summary>
var result = await GetProcessResultAsync(filename, arguments, ct).ConfigureAwait(false); /// <param name="filename">The filename.</param>
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError; /// <param name="arguments">The arguments.</param>
} /// <param name="workingDirectory">The working directory.</param>
/// <param name="ct">The cancellation token.</param>
/// <summary> /// <returns>
/// Gets the process output asynchronous. /// The type of the result produced by this Task.
/// </summary> /// </returns>
/// <param name="filename">The filename.</param> public static async Task<String> GetProcessOutputAsync(
/// <param name="arguments">The arguments.</param> String filename,
/// <param name="workingDirectory">The working directory.</param> String arguments,
/// <param name="ct">The cancellation token.</param> String workingDirectory,
/// <returns> CancellationToken ct = default) {
/// The type of the result produced by this Task. ProcessResult result = await GetProcessResultAsync(filename, arguments, workingDirectory, ct: ct).ConfigureAwait(false);
/// </returns> return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
public static async Task<string> GetProcessOutputAsync( }
string filename,
string arguments, /// <summary>
string workingDirectory, /// Runs the process asynchronously and if the exit code is 0,
CancellationToken ct = default) /// returns all of the standard output text. If the exit code is something other than 0
{ /// it returns the contents of standard error.
var result = await GetProcessResultAsync(filename, arguments, workingDirectory, ct: ct).ConfigureAwait(false); /// This method is meant to be used for programs that output a relatively small amount
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError; /// of text using a different encoder.
} /// </summary>
/// <param name="filename">The filename.</param>
/// <summary> /// <param name="arguments">The arguments.</param>
/// Runs the process asynchronously and if the exit code is 0, /// <param name="encoding">The encoding.</param>
/// returns all of the standard output text. If the exit code is something other than 0 /// <param name="ct">The cancellation token.</param>
/// it returns the contents of standard error. /// <returns>
/// This method is meant to be used for programs that output a relatively small amount /// The type of the result produced by this Task.
/// of text using a different encoder. /// </returns>
/// </summary> public static async Task<String> GetProcessEncodedOutputAsync(
/// <param name="filename">The filename.</param> String filename,
/// <param name="arguments">The arguments.</param> String arguments = "",
/// <param name="encoding">The encoding.</param> Encoding encoding = null,
/// <param name="ct">The cancellation token.</param> CancellationToken ct = default) {
/// <returns> ProcessResult result = await GetProcessResultAsync(filename, arguments, null, encoding, ct).ConfigureAwait(false);
/// The type of the result produced by this Task. return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
/// </returns> }
public static async Task<string> GetProcessEncodedOutputAsync(
string filename, /// <summary>
string arguments = "", /// Executes a process asynchronously and returns the text of the standard output and standard error streams
Encoding encoding = null, /// along with the exit code. This method is meant to be used for programs that output a relatively small
CancellationToken ct = default) /// amount of text.
{ /// </summary>
var result = await GetProcessResultAsync(filename, arguments, null, encoding, ct).ConfigureAwait(false); /// <param name="filename">The filename.</param>
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError; /// <param name="arguments">The arguments.</param>
} /// <param name="ct">The cancellation token.</param>
/// <returns>
/// <summary> /// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance.
/// Executes a process asynchronously and returns the text of the standard output and standard error streams /// </returns>
/// along with the exit code. This method is meant to be used for programs that output a relatively small /// <exception cref="ArgumentNullException">filename.</exception>
/// amount of text. public static Task<ProcessResult> GetProcessResultAsync(
/// </summary> String filename,
/// <param name="filename">The filename.</param> String arguments = "",
/// <param name="arguments">The arguments.</param> CancellationToken ct = default) =>
/// <param name="ct">The cancellation token.</param> GetProcessResultAsync(filename, arguments, null, Definitions.CurrentAnsiEncoding, ct);
/// <returns>
/// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance. /// <summary>
/// </returns> /// Executes a process asynchronously and returns the text of the standard output and standard error streams
/// <exception cref="ArgumentNullException">filename.</exception> /// along with the exit code. This method is meant to be used for programs that output a relatively small
public static Task<ProcessResult> GetProcessResultAsync( /// amount of text.
string filename, /// </summary>
string arguments = "", /// <param name="filename">The filename.</param>
CancellationToken ct = default) => /// <param name="arguments">The arguments.</param>
GetProcessResultAsync(filename, arguments, null, Definitions.CurrentAnsiEncoding, ct); /// <param name="workingDirectory">The working directory.</param>
/// <param name="encoding">The encoding.</param>
/// <summary> /// <param name="ct">The cancellation token.</param>
/// Executes a process asynchronously and returns the text of the standard output and standard error streams /// <returns>
/// along with the exit code. This method is meant to be used for programs that output a relatively small /// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance.
/// amount of text. /// </returns>
/// </summary> /// <exception cref="ArgumentNullException">filename.</exception>
/// <param name="filename">The filename.</param> /// <example>
/// <param name="arguments">The arguments.</param> /// The following code describes how to run an external process using the <see cref="GetProcessResultAsync(String, String, String, Encoding, CancellationToken)" /> method.
/// <param name="workingDirectory">The working directory.</param> /// <code>
/// <param name="encoding">The encoding.</param> /// class Example
/// <param name="ct">The cancellation token.</param> /// {
/// <returns> /// using System.Threading.Tasks;
/// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance. /// using Unosquare.Swan.Components;
/// </returns> /// static async Task Main()
/// <exception cref="ArgumentNullException">filename.</exception> /// {
/// <example> /// // Execute a process asynchronously
/// The following code describes how to run an external process using the <see cref="GetProcessResultAsync(string, string, string, Encoding, CancellationToken)" /> method. /// var data = await ProcessRunner.GetProcessResultAsync("dotnet", "--help");
/// <code> /// // print out the exit code
/// class Example /// $"{data.ExitCode}".WriteLine();
/// { /// // print out the output
/// using System.Threading.Tasks; /// data.StandardOutput.WriteLine();
/// using Unosquare.Swan.Components; /// // and the error if exists
/// static async Task Main() /// data.StandardError.Error();
/// { /// }
/// // Execute a process asynchronously /// }
/// var data = await ProcessRunner.GetProcessResultAsync("dotnet", "--help"); /// </code></example>
/// // print out the exit code public static async Task<ProcessResult> GetProcessResultAsync(
/// $"{data.ExitCode}".WriteLine(); String filename,
/// // print out the output String arguments,
/// data.StandardOutput.WriteLine(); String workingDirectory,
/// // and the error if exists Encoding encoding = null,
/// data.StandardError.Error(); CancellationToken ct = default) {
/// } if(filename == null) {
/// } throw new ArgumentNullException(nameof(filename));
/// </code></example> }
public static async Task<ProcessResult> GetProcessResultAsync(
string filename, if(encoding == null) {
string arguments, encoding = Definitions.CurrentAnsiEncoding;
string workingDirectory, }
Encoding encoding = null,
CancellationToken ct = default) StringBuilder standardOutputBuilder = new StringBuilder();
{ StringBuilder standardErrorBuilder = new StringBuilder();
if (filename == null)
throw new ArgumentNullException(nameof(filename)); Int32 processReturn = await RunProcessAsync(
filename,
if (encoding == null) arguments,
encoding = Definitions.CurrentAnsiEncoding; workingDirectory,
(data, proc) => standardOutputBuilder.Append(encoding.GetString(data)),
var standardOutputBuilder = new StringBuilder(); (data, proc) => standardErrorBuilder.Append(encoding.GetString(data)),
var standardErrorBuilder = new StringBuilder(); encoding,
true,
var processReturn = await RunProcessAsync( ct)
filename, .ConfigureAwait(false);
arguments,
workingDirectory, return new ProcessResult(processReturn, standardOutputBuilder.ToString(), standardErrorBuilder.ToString());
(data, proc) => { standardOutputBuilder.Append(encoding.GetString(data)); }, }
(data, proc) => { standardErrorBuilder.Append(encoding.GetString(data)); },
encoding, /// <summary>
true, /// Runs an external process asynchronously, providing callbacks to
ct) /// capture binary data from the standard error and standard output streams.
.ConfigureAwait(false); /// The callbacks contain a reference to the process so you can respond to output or
/// error streams by writing to the process' input stream.
return new ProcessResult(processReturn, standardOutputBuilder.ToString(), standardErrorBuilder.ToString()); /// The exit code (return value) will be -1 for forceful termination of the process.
} /// </summary>
/// <param name="filename">The filename.</param>
/// <summary> /// <param name="arguments">The arguments.</param>
/// Runs an external process asynchronously, providing callbacks to /// <param name="workingDirectory">The working directory.</param>
/// capture binary data from the standard error and standard output streams. /// <param name="onOutputData">The on output data.</param>
/// The callbacks contain a reference to the process so you can respond to output or /// <param name="onErrorData">The on error data.</param>
/// error streams by writing to the process' input stream. /// <param name="encoding">The encoding.</param>
/// The exit code (return value) will be -1 for forceful termination of the process. /// <param name="syncEvents">if set to <c>true</c> the next data callback will wait until the current one completes.</param>
/// </summary> /// <param name="ct">The cancellation token.</param>
/// <param name="filename">The filename.</param> /// <returns>
/// <param name="arguments">The arguments.</param> /// Value type will be -1 for forceful termination of the process.
/// <param name="workingDirectory">The working directory.</param> /// </returns>
/// <param name="onOutputData">The on output data.</param> public static Task<Int32> RunProcessAsync(
/// <param name="onErrorData">The on error data.</param> String filename,
/// <param name="encoding">The encoding.</param> String arguments,
/// <param name="syncEvents">if set to <c>true</c> the next data callback will wait until the current one completes.</param> String workingDirectory,
/// <param name="ct">The cancellation token.</param> ProcessDataReceivedCallback onOutputData,
/// <returns> ProcessDataReceivedCallback onErrorData,
/// Value type will be -1 for forceful termination of the process. Encoding encoding,
/// </returns> Boolean syncEvents = true,
public static Task<int> RunProcessAsync( CancellationToken ct = default) {
string filename, if(filename == null) {
string arguments, throw new ArgumentNullException(nameof(filename));
string workingDirectory, }
ProcessDataReceivedCallback onOutputData,
ProcessDataReceivedCallback onErrorData, return Task.Run(() => {
Encoding encoding, // Setup the process and its corresponding start info
bool syncEvents = true, Process process = new Process {
CancellationToken ct = default) EnableRaisingEvents = false,
{ StartInfo = new ProcessStartInfo {
if (filename == null) Arguments = arguments,
throw new ArgumentNullException(nameof(filename)); CreateNoWindow = true,
FileName = filename,
return Task.Run(() => RedirectStandardError = true,
{ StandardErrorEncoding = encoding,
// Setup the process and its corresponding start info RedirectStandardOutput = true,
var process = new Process StandardOutputEncoding = encoding,
{ UseShellExecute = false,
EnableRaisingEvents = false,
StartInfo = new ProcessStartInfo
{
Arguments = arguments,
CreateNoWindow = true,
FileName = filename,
RedirectStandardError = true,
StandardErrorEncoding = encoding,
RedirectStandardOutput = true,
StandardOutputEncoding = encoding,
UseShellExecute = false,
#if NET452 #if NET452
WindowStyle = ProcessWindowStyle.Hidden, WindowStyle = ProcessWindowStyle.Hidden,
#endif #endif
}, },
}; };
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
process.Start(); // Launch the process and discard any buffered data for standard error and standard output
process.StandardError.DiscardBufferedData(); process.Start();
process.StandardOutput.DiscardBufferedData(); process.StandardError.DiscardBufferedData();
process.StandardOutput.DiscardBufferedData();
// Launch the asynchronous stream reading tasks
var readTasks = new Task[2]; // Launch the asynchronous stream reading tasks
readTasks[0] = CopyStreamAsync( Task[] readTasks = new Task[2];
process, readTasks[0] = CopyStreamAsync(
process.StandardOutput.BaseStream, process,
onOutputData, process.StandardOutput.BaseStream,
syncEvents, onOutputData,
ct); syncEvents,
readTasks[1] = CopyStreamAsync( ct);
process, readTasks[1] = CopyStreamAsync(
process.StandardError.BaseStream, process,
onErrorData, process.StandardError.BaseStream,
syncEvents, onErrorData,
ct); syncEvents,
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
{ } finally {
// ignore // Wait for the process to exit
} while(ct.IsCancellationRequested == false) {
finally if(process.HasExited || process.WaitForExit(5)) {
{ break;
// Wait for the process to exit }
while (ct.IsCancellationRequested == false) }
{
if (process.HasExited || process.WaitForExit(5)) // Forcefully kill the process if it do not exit
break; try {
} if(process.HasExited == false) {
process.Kill();
// Forcefully kill the process if it do not exit }
try } catch {
{ // swallow
if (process.HasExited == false) }
process.Kill(); }
}
catch try {
{ // Retrieve and return the exit code.
// swallow // -1 signals error
} return process.HasExited ? process.ExitCode : -1;
} } catch {
return -1;
try }
{ }, ct);
// Retrieve and return the exit code. }
// -1 signals error
return process.HasExited ? process.ExitCode : -1; /// <summary>
} /// Runs an external process asynchronously, providing callbacks to
catch /// capture binary data from the standard error and standard output streams.
{ /// The callbacks contain a reference to the process so you can respond to output or
return -1; /// error streams by writing to the process' input stream.
} /// The exit code (return value) will be -1 for forceful termination of the process.
}, ct); /// </summary>
} /// <param name="filename">The filename.</param>
/// <param name="arguments">The arguments.</param>
/// <summary> /// <param name="onOutputData">The on output data.</param>
/// Runs an external process asynchronously, providing callbacks to /// <param name="onErrorData">The on error data.</param>
/// capture binary data from the standard error and standard output streams. /// <param name="syncEvents">if set to <c>true</c> the next data callback will wait until the current one completes.</param>
/// The callbacks contain a reference to the process so you can respond to output or /// <param name="ct">The cancellation token.</param>
/// error streams by writing to the process' input stream. /// <returns>Value type will be -1 for forceful termination of the process.</returns>
/// The exit code (return value) will be -1 for forceful termination of the process. /// <example>
/// </summary> /// The following example illustrates how to run an external process using the
/// <param name="filename">The filename.</param> /// <see cref="RunProcessAsync(String, String, ProcessDataReceivedCallback, ProcessDataReceivedCallback, Boolean, CancellationToken)"/>
/// <param name="arguments">The arguments.</param> /// method.
/// <param name="onOutputData">The on output data.</param> /// <code>
/// <param name="onErrorData">The on error data.</param> /// class Example
/// <param name="syncEvents">if set to <c>true</c> the next data callback will wait until the current one completes.</param> /// {
/// <param name="ct">The cancellation token.</param> /// using System.Diagnostics;
/// <returns>Value type will be -1 for forceful termination of the process.</returns> /// using System.Text;
/// <example> /// using System.Threading.Tasks;
/// The following example illustrates how to run an external process using the /// using Unosquare.Swan;
/// <see cref="RunProcessAsync(string, string, ProcessDataReceivedCallback, ProcessDataReceivedCallback, bool, CancellationToken)"/> /// using Unosquare.Swan.Components;
/// method. ///
/// <code> /// static async Task Main()
/// class Example /// {
/// { /// // Execute a process asynchronously
/// using System.Diagnostics; /// var data = await ProcessRunner
/// using System.Text; /// .RunProcessAsync("dotnet", "--help", Print, Print);
/// using System.Threading.Tasks; ///
/// using Unosquare.Swan; /// // flush all messages
/// using Unosquare.Swan.Components; /// Terminal.Flush();
/// /// }
/// static async Task Main() ///
/// { /// // a callback to print both output or errors
/// // Execute a process asynchronously /// static void Print(byte[] data, Process proc) =>
/// var data = await ProcessRunner /// Encoding.GetEncoding(0).GetString(data).WriteLine();
/// .RunProcessAsync("dotnet", "--help", Print, Print); /// }
/// /// </code>
/// // flush all messages /// </example>
/// Terminal.Flush(); public static Task<Int32> RunProcessAsync(
/// } String filename,
/// String arguments,
/// // a callback to print both output or errors ProcessDataReceivedCallback onOutputData,
/// static void Print(byte[] data, Process proc) => ProcessDataReceivedCallback onErrorData,
/// Encoding.GetEncoding(0).GetString(data).WriteLine(); Boolean syncEvents = true,
/// } CancellationToken ct = default)
/// </code> => RunProcessAsync(
/// </example> filename,
public static Task<int> RunProcessAsync( arguments,
string filename, null,
string arguments, onOutputData,
ProcessDataReceivedCallback onOutputData, onErrorData,
ProcessDataReceivedCallback onErrorData, Definitions.CurrentAnsiEncoding,
bool syncEvents = true, syncEvents,
CancellationToken ct = default) ct);
=> RunProcessAsync(
filename, /// <summary>
arguments, /// Copies the stream asynchronously.
null, /// </summary>
onOutputData, /// <param name="process">The process.</param>
onErrorData, /// <param name="baseStream">The source stream.</param>
Definitions.CurrentAnsiEncoding, /// <param name="onDataCallback">The on data callback.</param>
syncEvents, /// <param name="syncEvents">if set to <c>true</c> [synchronize events].</param>
ct); /// <param name="ct">The cancellation token.</param>
/// <returns>Total copies stream.</returns>
/// <summary> private static Task<UInt64> CopyStreamAsync(
/// Copies the stream asynchronously. Process process,
/// </summary> Stream baseStream,
/// <param name="process">The process.</param> ProcessDataReceivedCallback onDataCallback,
/// <param name="baseStream">The source stream.</param> Boolean syncEvents,
/// <param name="onDataCallback">The on data callback.</param> CancellationToken ct) => Task.Factory.StartNew(async () => {
/// <param name="syncEvents">if set to <c>true</c> [synchronize events].</param> // define some state variables
/// <param name="ct">The cancellation token.</param> Byte[] swapBuffer = new Byte[2048]; // the buffer to copy data from one stream to the next
/// <returns>Total copies stream.</returns> UInt64 totalCount = 0; // the total amount of bytes read
private static Task<ulong> CopyStreamAsync( Boolean hasExited = false;
Process process,
Stream baseStream, while(ct.IsCancellationRequested == false) {
ProcessDataReceivedCallback onDataCallback, try {
bool syncEvents, // Check if process is no longer valid
CancellationToken ct) // if this condition holds, simply read the last bits of data available.
{ Int32 readCount; // the bytes read in any given event
return Task.Factory.StartNew(async () => if(process.HasExited || process.WaitForExit(1)) {
{ while(true) {
// define some state variables try {
var swapBuffer = new byte[2048]; // the buffer to copy data from one stream to the next readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
ulong totalCount = 0; // the total amount of bytes read
var hasExited = false; if(readCount > 0) {
totalCount += (UInt64)readCount;
while (ct.IsCancellationRequested == false) onDataCallback?.Invoke(swapBuffer.Skip(0).Take(readCount).ToArray(), process);
{ } else {
try hasExited = true;
{ break;
// Check if process is no longer valid }
// if this condition holds, simply read the last bits of data available. } catch {
int readCount; // the bytes read in any given event hasExited = true;
if (process.HasExited || process.WaitForExit(1)) break;
{ }
while (true) }
{ }
try
{ if(hasExited) {
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct); break;
}
if (readCount > 0)
{ // Try reading from the stream. < 0 means no read occurred.
totalCount += (ulong) readCount; readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
onDataCallback?.Invoke(swapBuffer.Skip(0).Take(readCount).ToArray(), process);
} // When no read is done, we need to let is rest for a bit
else if(readCount <= 0) {
{ await Task.Delay(1, ct); // do not hog CPU cycles doing nothing.
hasExited = true; continue;
break; }
}
} totalCount += (UInt64)readCount;
catch if(onDataCallback == null) {
{ continue;
hasExited = true; }
break;
} // Create the buffer to pass to the callback
} Byte[] eventBuffer = swapBuffer.Skip(0).Take(readCount).ToArray();
}
// Create the data processing callback invocation
if (hasExited) break; Task eventTask =
Task.Factory.StartNew(() => onDataCallback.Invoke(eventBuffer, process), ct);
// Try reading from the stream. < 0 means no read occurred.
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct); // wait for the event to process before the next read occurs
if(syncEvents) {
// When no read is done, we need to let is rest for a bit eventTask.Wait(ct);
if (readCount <= 0) }
{ } catch {
await Task.Delay(1, ct); // do not hog CPU cycles doing nothing. break;
continue; }
} }
totalCount += (ulong) readCount; return totalCount;
if (onDataCallback == null) continue; }, ct).Unwrap();
}
// Create the buffer to pass to the callback
var eventBuffer = swapBuffer.Skip(0).Take(readCount).ToArray();
// Create the data processing callback invocation
var eventTask =
Task.Factory.StartNew(() => { onDataCallback.Invoke(eventBuffer, process); }, ct);
// wait for the event to process before the next read occurs
if (syncEvents) eventTask.Wait(ct);
}
catch
{
break;
}
}
return totalCount;
}, ct).Unwrap();
}
}
} }

View File

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

View File

@ -1,132 +1,120 @@
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>
/// Registration options for "fluent" API.
/// </summary>
public sealed class RegisterOptions {
private readonly TypesConcurrentDictionary _registeredTypes;
private readonly DependencyContainer.TypeRegistration _registration;
/// <summary> /// <summary>
/// Registration options for "fluent" API. /// Initializes a new instance of the <see cref="RegisterOptions" /> class.
/// </summary> /// </summary>
public sealed class RegisterOptions /// <param name="registeredTypes">The registered types.</param>
{ /// <param name="registration">The registration.</param>
private readonly TypesConcurrentDictionary _registeredTypes; public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration) {
private readonly DependencyContainer.TypeRegistration _registration; this._registeredTypes = registeredTypes;
this._registration = registration;
/// <summary> }
/// Initializes a new instance of the <see cref="RegisterOptions" /> class.
/// </summary>
/// <param name="registeredTypes">The registered types.</param>
/// <param name="registration">The registration.</param>
public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration)
{
_registeredTypes = registeredTypes;
_registration = registration;
}
/// <summary>
/// Make registration a singleton (single instance) if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions AsSingleton()
{
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null)
throw new DependencyContainerRegistrationException(_registration.Type, "singleton");
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.SingletonVariant);
}
/// <summary>
/// Make registration multi-instance if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions AsMultiInstance()
{
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null)
throw new DependencyContainerRegistrationException(_registration.Type, "multi-instance");
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.MultiInstanceVariant);
}
/// <summary>
/// Make registration hold a weak reference if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions WithWeakReference()
{
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null)
throw new DependencyContainerRegistrationException(_registration.Type, "weak reference");
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.WeakReferenceVariant);
}
/// <summary>
/// Make registration hold a strong reference if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions WithStrongReference()
{
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null)
throw new DependencyContainerRegistrationException(_registration.Type, "strong reference");
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.StrongReferenceVariant);
}
}
/// <summary> /// <summary>
/// Registration options for "fluent" API when registering multiple implementations. /// Make registration a singleton (single instance) if possible.
/// </summary> /// </summary>
public sealed class MultiRegisterOptions /// <returns>A registration options for fluent API.</returns>
{ /// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
private IEnumerable<RegisterOptions> _registerOptions; public RegisterOptions AsSingleton() {
ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
/// <summary>
/// Initializes a new instance of the <see cref="MultiRegisterOptions"/> class. if(currentFactory == null) {
/// </summary> throw new DependencyContainerRegistrationException(this._registration.Type, "singleton");
/// <param name="registerOptions">The register options.</param> }
public MultiRegisterOptions(IEnumerable<RegisterOptions> registerOptions)
{ return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.SingletonVariant);
_registerOptions = registerOptions; }
}
/// <summary>
/// <summary> /// Make registration multi-instance if possible.
/// Make registration a singleton (single instance) if possible. /// </summary>
/// </summary> /// <returns>A registration options 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 RegisterOptions AsMultiInstance() {
public MultiRegisterOptions AsSingleton() ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
{
_registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsSingleton()); if(currentFactory == null) {
return this; throw new DependencyContainerRegistrationException(this._registration.Type, "multi-instance");
} }
/// <summary> return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.MultiInstanceVariant);
/// Make registration multi-instance if possible. }
/// </summary>
/// <returns>A registration multi-instance for fluent API.</returns> /// <summary>
/// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception> /// Make registration hold a weak reference if possible.
public MultiRegisterOptions AsMultiInstance() /// </summary>
{ /// <returns>A registration options for fluent API.</returns>
_registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance()); /// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
return this; public RegisterOptions WithWeakReference() {
} ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
private IEnumerable<RegisterOptions> ExecuteOnAllRegisterOptions( if(currentFactory == null) {
Func<RegisterOptions, RegisterOptions> action) throw new DependencyContainerRegistrationException(this._registration.Type, "weak reference");
{ }
return _registerOptions.Select(action).ToList();
} return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.WeakReferenceVariant);
} }
/// <summary>
/// Make registration hold a strong reference if possible.
/// </summary>
/// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions WithStrongReference() {
ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
if(currentFactory == null) {
throw new DependencyContainerRegistrationException(this._registration.Type, "strong reference");
}
return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.StrongReferenceVariant);
}
}
/// <summary>
/// Registration options for "fluent" API when registering multiple implementations.
/// </summary>
public sealed class MultiRegisterOptions {
private IEnumerable<RegisterOptions> _registerOptions;
/// <summary>
/// Initializes a new instance of the <see cref="MultiRegisterOptions"/> class.
/// </summary>
/// <param name="registerOptions">The register options.</param>
public MultiRegisterOptions(IEnumerable<RegisterOptions> registerOptions) => this._registerOptions = registerOptions;
/// <summary>
/// Make registration a singleton (single instance) if possible.
/// </summary>
/// <returns>A registration multi-instance for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception>
public MultiRegisterOptions AsSingleton() {
this._registerOptions = this.ExecuteOnAllRegisterOptions(ro => ro.AsSingleton());
return this;
}
/// <summary>
/// Make registration multi-instance if possible.
/// </summary>
/// <returns>A registration multi-instance for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception>
public MultiRegisterOptions AsMultiInstance() {
this._registerOptions = this.ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance());
return this;
}
private IEnumerable<RegisterOptions> ExecuteOnAllRegisterOptions(
Func<RegisterOptions, RegisterOptions> action) => this._registerOptions.Select(action).ToList();
}
} }

View File

@ -1,67 +1,63 @@
namespace Unosquare.Swan.Components using System;
{
using System; namespace Unosquare.Swan.Components {
public partial class DependencyContainer {
public partial class DependencyContainer /// <summary>
{ /// Represents a Type Registration within the IoC Container.
/// <summary> /// </summary>
/// Represents a Type Registration within the IoC Container. public sealed class TypeRegistration {
/// </summary> private readonly Int32 _hashCode;
public sealed class TypeRegistration
{ /// <summary>
private readonly int _hashCode; /// Initializes a new instance of the <see cref="TypeRegistration"/> class.
/// </summary>
/// <summary> /// <param name="type">The type.</param>
/// Initializes a new instance of the <see cref="TypeRegistration"/> class. /// <param name="name">The name.</param>
/// </summary> public TypeRegistration(Type type, String name = null) {
/// <param name="type">The type.</param> this.Type = type;
/// <param name="name">The name.</param> this.Name = name ?? String.Empty;
public TypeRegistration(Type type, string name = null)
{ this._hashCode = String.Concat(this.Type.FullName, "|", this.Name).GetHashCode();
Type = type; }
Name = name ?? string.Empty;
/// <summary>
_hashCode = string.Concat(Type.FullName, "|", Name).GetHashCode(); /// Gets the type.
} /// </summary>
/// <value>
/// <summary> /// The type.
/// Gets the type. /// </value>
/// </summary> public Type Type {
/// <value> get;
/// The type. }
/// </value>
public Type Type { get; } /// <summary>
/// Gets the name.
/// <summary> /// </summary>
/// Gets the name. /// <value>
/// </summary> /// The name.
/// <value> /// </value>
/// The name. public String Name {
/// </value> 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.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param> /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
/// <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;
/// <summary>
return string.Compare(Name, typeRegistration.Name, StringComparison.Ordinal) == 0; /// Returns a hash code for this instance.
} /// </summary>
/// <returns>
/// <summary> /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// Returns a hash code for this instance. /// </returns>
/// </summary> public override Int32 GetHashCode() => this._hashCode;
/// <returns> }
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. }
/// </returns>
public override int GetHashCode() => _hashCode;
}
}
} }

View File

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

View File

@ -1,28 +1,26 @@
namespace Unosquare.Swan namespace Unosquare.Swan {
{ /// <summary>
/// Enumerates the possible causes of the DataReceived event occurring.
/// </summary>
public enum ConnectionDataReceivedTrigger {
/// <summary> /// <summary>
/// Enumerates the possible causes of the DataReceived event occurring. /// The trigger was a forceful flush of the buffer
/// </summary> /// </summary>
public enum ConnectionDataReceivedTrigger Flush,
{
/// <summary> /// <summary>
/// The trigger was a forceful flush of the buffer /// The new line sequence bytes were received
/// </summary> /// </summary>
Flush, NewLineSequenceEncountered,
/// <summary> /// <summary>
/// The new line sequence bytes were received /// The buffer was full
/// </summary> /// </summary>
NewLineSequenceEncountered, BufferFull,
/// <summary> /// <summary>
/// The buffer was full /// The block size reached
/// </summary> /// </summary>
BufferFull, BlockSizeReached,
}
/// <summary>
/// The block size reached
/// </summary>
BlockSizeReached,
}
} }

View File

@ -1,158 +1,157 @@
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>
/// The event arguments for when connections are accepted.
/// </summary>
/// <seealso cref="System.EventArgs" />
public class ConnectionAcceptedEventArgs : EventArgs {
/// <summary> /// <summary>
/// The event arguments for when connections are accepted. /// Initializes a new instance of the <see cref="ConnectionAcceptedEventArgs" /> class.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <param name="client">The client.</param>
public class ConnectionAcceptedEventArgs : EventArgs /// <exception cref="ArgumentNullException">client.</exception>
{ public ConnectionAcceptedEventArgs(TcpClient client) => this.Client = client ?? throw new ArgumentNullException(nameof(client));
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionAcceptedEventArgs" /> class.
/// </summary>
/// <param name="client">The client.</param>
/// <exception cref="ArgumentNullException">client.</exception>
public ConnectionAcceptedEventArgs(TcpClient client)
{
Client = client ?? throw new ArgumentNullException(nameof(client));
}
/// <summary>
/// Gets the client.
/// </summary>
/// <value>
/// The client.
/// </value>
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. /// Gets the client.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.ConnectionAcceptedEventArgs" /> /// <value>
public class ConnectionAcceptingEventArgs : ConnectionAcceptedEventArgs /// The client.
{ /// </value>
/// <summary> public TcpClient Client {
/// Initializes a new instance of the <see cref="ConnectionAcceptingEventArgs"/> class. get;
/// </summary> }
/// <param name="client">The client.</param> }
public ConnectionAcceptingEventArgs(TcpClient client)
: base(client) /// <summary>
{ /// Occurs before a connection is accepted. Set the Cancel property to true to prevent the connection from being accepted.
} /// </summary>
/// <seealso cref="Unosquare.Swan.ConnectionAcceptedEventArgs" />
/// <summary> public class ConnectionAcceptingEventArgs : ConnectionAcceptedEventArgs {
/// Setting Cancel to true rejects the new TcpClient.
/// </summary>
/// <value>
/// <c>true</c> if cancel; otherwise, <c>false</c>.
/// </value>
public bool Cancel { get; set; }
}
/// <summary> /// <summary>
/// Event arguments for when a server listener is started. /// Initializes a new instance of the <see cref="ConnectionAcceptingEventArgs"/> class.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <param name="client">The client.</param>
public class ConnectionListenerStartedEventArgs : EventArgs public ConnectionAcceptingEventArgs(TcpClient client)
{ : base(client) {
/// <summary> }
/// Initializes a new instance of the <see cref="ConnectionListenerStartedEventArgs" /> class.
/// </summary>
/// <param name="listenerEndPoint">The listener end point.</param>
/// <exception cref="ArgumentNullException">listenerEndPoint.</exception>
public ConnectionListenerStartedEventArgs(IPEndPoint listenerEndPoint)
{
EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
}
/// <summary>
/// Gets the end point.
/// </summary>
/// <value>
/// The end point.
/// </value>
public IPEndPoint EndPoint { get; }
}
/// <summary> /// <summary>
/// Event arguments for when a server listener fails to start. /// Setting Cancel to true rejects the new TcpClient.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <value>
public class ConnectionListenerFailedEventArgs : EventArgs /// <c>true</c> if cancel; otherwise, <c>false</c>.
{ /// </value>
/// <summary> public Boolean Cancel {
/// Initializes a new instance of the <see cref="ConnectionListenerFailedEventArgs" /> class. get; set;
/// </summary> }
/// <param name="listenerEndPoint">The listener end point.</param> }
/// <param name="ex">The ex.</param>
/// <exception cref="ArgumentNullException"> /// <summary>
/// listenerEndPoint /// Event arguments for when a server listener is started.
/// or /// </summary>
/// ex. /// <seealso cref="System.EventArgs" />
/// </exception> public class ConnectionListenerStartedEventArgs : EventArgs {
public ConnectionListenerFailedEventArgs(IPEndPoint listenerEndPoint, Exception ex)
{
EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
Error = ex ?? throw new ArgumentNullException(nameof(ex));
}
/// <summary>
/// Gets the end point.
/// </summary>
/// <value>
/// The end point.
/// </value>
public IPEndPoint EndPoint { get; }
/// <summary>
/// Gets the error.
/// </summary>
/// <value>
/// The error.
/// </value>
public Exception Error { get; }
}
/// <summary> /// <summary>
/// Event arguments for when a server listener stopped. /// Initializes a new instance of the <see cref="ConnectionListenerStartedEventArgs" /> class.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <param name="listenerEndPoint">The listener end point.</param>
public class ConnectionListenerStoppedEventArgs : EventArgs /// <exception cref="ArgumentNullException">listenerEndPoint.</exception>
{ public ConnectionListenerStartedEventArgs(IPEndPoint listenerEndPoint) => this.EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionListenerStoppedEventArgs" /> class. /// <summary>
/// </summary> /// Gets the end point.
/// <param name="listenerEndPoint">The listener end point.</param> /// </summary>
/// <param name="ex">The ex.</param> /// <value>
/// <exception cref="ArgumentNullException"> /// The end point.
/// listenerEndPoint /// </value>
/// or public IPEndPoint EndPoint {
/// ex. get;
/// </exception> }
public ConnectionListenerStoppedEventArgs(IPEndPoint listenerEndPoint, Exception ex = null) }
{
EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint)); /// <summary>
Error = ex; /// Event arguments for when a server listener fails to start.
} /// </summary>
/// <seealso cref="System.EventArgs" />
/// <summary> public class ConnectionListenerFailedEventArgs : EventArgs {
/// Gets the end point. /// <summary>
/// </summary> /// Initializes a new instance of the <see cref="ConnectionListenerFailedEventArgs" /> class.
/// <value> /// </summary>
/// The end point. /// <param name="listenerEndPoint">The listener end point.</param>
/// </value> /// <param name="ex">The ex.</param>
public IPEndPoint EndPoint { get; } /// <exception cref="ArgumentNullException">
/// listenerEndPoint
/// <summary> /// or
/// Gets the error. /// ex.
/// </summary> /// </exception>
/// <value> public ConnectionListenerFailedEventArgs(IPEndPoint listenerEndPoint, Exception ex) {
/// The error. this.EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
/// </value> this.Error = ex ?? throw new ArgumentNullException(nameof(ex));
public Exception Error { get; } }
}
/// <summary>
/// Gets the end point.
/// </summary>
/// <value>
/// The end point.
/// </value>
public IPEndPoint EndPoint {
get;
}
/// <summary>
/// Gets the error.
/// </summary>
/// <value>
/// The error.
/// </value>
public Exception Error {
get;
}
}
/// <summary>
/// Event arguments for when a server listener stopped.
/// </summary>
/// <seealso cref="System.EventArgs" />
public class ConnectionListenerStoppedEventArgs : EventArgs {
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionListenerStoppedEventArgs" /> class.
/// </summary>
/// <param name="listenerEndPoint">The listener end point.</param>
/// <param name="ex">The ex.</param>
/// <exception cref="ArgumentNullException">
/// listenerEndPoint
/// or
/// ex.
/// </exception>
public ConnectionListenerStoppedEventArgs(IPEndPoint listenerEndPoint, Exception ex = null) {
this.EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
this.Error = ex;
}
/// <summary>
/// Gets the end point.
/// </summary>
/// <value>
/// The end point.
/// </value>
public IPEndPoint EndPoint {
get;
}
/// <summary>
/// Gets the error.
/// </summary>
/// <value>
/// The error.
/// </value>
public Exception Error {
get;
}
}
} }

View File

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

View File

@ -1,46 +1,39 @@
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>
/// Generic Constraint Registration Exception.
/// </summary>
/// <seealso cref="Exception" />
public class DependencyContainerRegistrationException : Exception {
private const String ConvertErrorText = "Cannot convert current registration of {0} to {1}";
private const String RegisterErrorText =
"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}).";
/// <summary> /// <summary>
/// Generic Constraint Registration Exception. /// Initializes a new instance of the <see cref="DependencyContainerRegistrationException"/> class.
/// </summary> /// </summary>
/// <seealso cref="Exception" /> /// <param name="registerType">Type of the register.</param>
public class DependencyContainerRegistrationException : Exception /// <param name="types">The types.</param>
{ public DependencyContainerRegistrationException(Type registerType, IEnumerable<Type> types)
private const string ConvertErrorText = "Cannot convert current registration of {0} to {1}"; : base(String.Format(ErrorText, registerType, GetTypesString(types))) {
private const string RegisterErrorText = }
"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})."; /// <summary>
/// Initializes a new instance of the <see cref="DependencyContainerRegistrationException" /> class.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="DependencyContainerRegistrationException"/> class. /// <param name="type">The type.</param>
/// </summary> /// <param name="method">The method.</param>
/// <param name="registerType">Type of the register.</param> /// <param name="isTypeFactory">if set to <c>true</c> [is type factory].</param>
/// <param name="types">The types.</param> public DependencyContainerRegistrationException(Type type, String method, Boolean isTypeFactory = false)
public DependencyContainerRegistrationException(Type registerType, IEnumerable<Type> types) : base(isTypeFactory
: base(string.Format(ErrorText, registerType, GetTypesString(types))) ? String.Format(RegisterErrorText, type.FullName, method)
{ : String.Format(ConvertErrorText, type.FullName, method)) {
} }
/// <summary> private static String GetTypesString(IEnumerable<Type> types) => String.Join(",", types.Select(type => type.FullName));
/// Initializes a new instance of the <see cref="DependencyContainerRegistrationException" /> class. }
/// </summary>
/// <param name="type">The type.</param>
/// <param name="method">The method.</param>
/// <param name="isTypeFactory">if set to <c>true</c> [is type factory].</param>
public DependencyContainerRegistrationException(Type type, string method, bool isTypeFactory = false)
: base(isTypeFactory
? string.Format(RegisterErrorText, type.FullName, method)
: string.Format(ConvertErrorText, type.FullName, method))
{
}
private static string GetTypesString(IEnumerable<Type> types)
{
return string.Join(",", types.Select(type => type.FullName));
}
}
} }

View File

@ -1,32 +1,28 @@
namespace Unosquare.Swan.Exceptions using System;
{
using System; namespace Unosquare.Swan.Exceptions {
/// <summary>
/// An exception for dependency resolutions.
/// </summary>
/// <seealso cref="System.Exception" />
public class DependencyContainerResolutionException : Exception {
private const String ErrorText = "Unable to resolve type: {0}";
/// <summary> /// <summary>
/// An exception for dependency resolutions. /// Initializes a new instance of the <see cref="DependencyContainerResolutionException"/> class.
/// </summary> /// </summary>
/// <seealso cref="System.Exception" /> /// <param name="type">The type.</param>
public class DependencyContainerResolutionException : Exception public DependencyContainerResolutionException(Type type)
{ : base(String.Format(ErrorText, type.FullName)) {
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) /// <param name="innerException">The inner exception.</param>
: base(string.Format(ErrorText, type.FullName)) public DependencyContainerResolutionException(Type type, Exception innerException)
{ : base(String.Format(ErrorText, type.FullName), innerException) {
} }
}
/// <summary>
/// Initializes a new instance of the <see cref="DependencyContainerResolutionException"/> class.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="innerException">The inner exception.</param>
public DependencyContainerResolutionException(Type type, Exception 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>
/// Weak Reference Exception.
/// </summary>
/// <seealso cref="System.Exception" />
public class DependencyContainerWeakReferenceException : Exception {
private const String ErrorText = "Unable to instantiate {0} - referenced object has been reclaimed";
/// <summary> /// <summary>
/// Weak Reference Exception. /// Initializes a new instance of the <see cref="DependencyContainerWeakReferenceException"/> class.
/// </summary> /// </summary>
/// <seealso cref="System.Exception" /> /// <param name="type">The type.</param>
public class DependencyContainerWeakReferenceException : Exception public DependencyContainerWeakReferenceException(Type type)
{ : base(String.Format(ErrorText, type.FullName)) {
private const string ErrorText = "Unable to instantiate {0} - referenced object has been reclaimed"; }
}
/// <summary>
/// Initializes a new instance of the <see cref="DependencyContainerWeakReferenceException"/> class.
/// </summary>
/// <param name="type">The type.</param>
public DependencyContainerWeakReferenceException(Type type)
: 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)
{ : base(message) {
internal DnsQueryException(string message) }
: base(message)
{ internal DnsQueryException(String message, Exception e)
} : base(message, e) {
}
internal DnsQueryException(string message, Exception e)
: base(message, e) internal DnsQueryException(DnsClient.IDnsResponse response)
{ : this(response, Format(response)) {
} }
internal DnsQueryException(DnsClient.IDnsResponse response) internal DnsQueryException(DnsClient.IDnsResponse response, String message)
: this(response, Format(response)) : base(message) => this.Response = response;
{
} internal DnsClient.IDnsResponse Response {
get;
internal DnsQueryException(DnsClient.IDnsResponse response, string message) }
: base(message)
{ private static String Format(DnsClient.IDnsResponse response) => $"Invalid response received with code {response.ResponseCode}";
Response = response; }
}
internal DnsClient.IDnsResponse Response { get; }
private static string Format(DnsClient.IDnsResponse response)
{
return $"Invalid response received with code {response.ResponseCode}";
}
}
} }

View File

@ -1,48 +1,49 @@
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> /// <seealso cref="System.Exception" />
/// <seealso cref="System.Exception" /> #if !NETSTANDARD1_3
#if !NETSTANDARD1_3 [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. /// </summary>
/// </summary> /// <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, Int32 httpErrorCode = 500, String errorContent = null)
public JsonRequestException(string message, int httpErrorCode = 500, string errorContent = null) : base(message) {
: base(message) this.HttpErrorCode = httpErrorCode;
{ this.HttpErrorContent = errorContent;
HttpErrorCode = httpErrorCode; }
HttpErrorContent = errorContent;
} /// <summary>
/// Gets the HTTP error code.
/// <summary> /// </summary>
/// Gets the HTTP error code. /// <value>
/// </summary> /// The HTTP error code.
/// <value> /// </value>
/// The HTTP error code. public Int32 HttpErrorCode {
/// </value> get;
public int HttpErrorCode { get; } }
/// <summary> /// <summary>
/// Gets the content of the HTTP error. /// Gets the content of the HTTP error.
/// </summary> /// </summary>
/// <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 /> }
public override string ToString() => string.IsNullOrEmpty(HttpErrorContent) ? $"HTTP Response Status Code {HttpErrorCode} Error Message: {HttpErrorContent}" : base.ToString();
} /// <inheritdoc />
public override String ToString() => String.IsNullOrEmpty(this.HttpErrorContent) ? $"HTTP Response Status Code {this.HttpErrorCode} Error Message: {this.HttpErrorContent}" : base.ToString();
}
} }

View File

@ -1,134 +1,133 @@
namespace Unosquare.Swan.Exceptions using System;
{ using Unosquare.Swan.Networking.Ldap;
using System;
using Networking.Ldap; namespace Unosquare.Swan.Exceptions {
/// <summary>
/// Thrown to indicate that an Ldap exception has occurred. This is a general
/// exception which includes a message and an Ldap result code.
/// An LdapException can result from physical problems (such as
/// network errors) as well as problems with Ldap operations detected
/// by the server. For example, if an Ldap add operation fails because of a
/// duplicate entry, the server returns a result code.
/// </summary>
/// <seealso cref="System.Exception" />
public class LdapException
: Exception {
internal const String UnexpectedEnd = "Unexpected end of filter";
internal const String MissingLeftParen = "Unmatched parentheses, left parenthesis missing";
internal const String MissingRightParen = "Unmatched parentheses, right parenthesis missing";
internal const String ExpectingRightParen = "Expecting right parenthesis, found \"{0}\"";
internal const String ExpectingLeftParen = "Expecting left parenthesis, found \"{0}\"";
private readonly String _serverMessage;
/// <summary> /// <summary>
/// Thrown to indicate that an Ldap exception has occurred. This is a general /// Initializes a new instance of the <see cref="LdapException" /> class.
/// exception which includes a message and an Ldap result code. /// Constructs an exception with a detailed message obtained from the
/// An LdapException can result from physical problems (such as /// specified <c>MessageOrKey</c> String.
/// network errors) as well as problems with Ldap operations detected /// Additional parameters specify the result code, the message returned
/// by the server. For example, if an Ldap add operation fails because of a /// from the server, and a matchedDN returned from the server.
/// duplicate entry, the server returns a result code. /// The String is used either as a message key to obtain a localized
/// message from ExceptionMessages, or if there is no key in the
/// resource matching the text, it is used as the detailed message itself.
/// </summary> /// </summary>
/// <seealso cref="System.Exception" /> /// <param name="message">The message.</param>
public class LdapException /// <param name="resultCode">The result code returned.</param>
: Exception /// <param name="serverMsg">Error message specifying additional information
{ /// from the server.</param>
internal const string UnexpectedEnd = "Unexpected end of filter"; /// <param name="matchedDN">The maximal subset of a specified DN which could
internal const string MissingLeftParen = "Unmatched parentheses, left parenthesis missing"; /// be matched by the server on a search operation.</param>
internal const string MissingRightParen = "Unmatched parentheses, right parenthesis missing"; /// <param name="rootException">The root exception.</param>
internal const string ExpectingRightParen = "Expecting right parenthesis, found \"{0}\""; public LdapException(
internal const string ExpectingLeftParen = "Expecting left parenthesis, found \"{0}\""; String message,
LdapStatusCode resultCode,
private readonly string _serverMessage; String serverMsg = null,
String matchedDN = null,
/// <summary> Exception rootException = null)
/// Initializes a new instance of the <see cref="LdapException" /> class. : base(message) {
/// Constructs an exception with a detailed message obtained from the this.ResultCode = resultCode;
/// specified <c>MessageOrKey</c> String. this.Cause = rootException;
/// Additional parameters specify the result code, the message returned this.MatchedDN = matchedDN;
/// from the server, and a matchedDN returned from the server. this._serverMessage = serverMsg;
/// The String is used either as a message key to obtain a localized }
/// message from ExceptionMessages, or if there is no key in the
/// resource matching the text, it is used as the detailed message itself. /// <summary>
/// </summary> /// Returns the error message from the Ldap server, if this message is
/// <param name="message">The message.</param> /// available (that is, if this message was set). If the message was not set,
/// <param name="resultCode">The result code returned.</param> /// this method returns null.
/// <param name="serverMsg">Error message specifying additional information /// </summary>
/// from the server.</param> /// <returns>
/// <param name="matchedDN">The maximal subset of a specified DN which could /// The error message or null if the message was not set.
/// be matched by the server on a search operation.</param> /// </returns>
/// <param name="rootException">The root exception.</param> public String LdapErrorMessage =>
public LdapException( this._serverMessage != null && this._serverMessage.Length == 0 ? null : this._serverMessage;
string message,
LdapStatusCode resultCode, /// <summary>
string serverMsg = null, /// Returns the lower level Exception which caused the failure, if any.
string matchedDN = null, /// For example, an IOException with additional information may be returned
Exception rootException = null) /// on a CONNECT_ERROR failure.
: base(message) /// </summary>
{ /// <value>
ResultCode = resultCode; /// The cause.
Cause = rootException; /// </value>
MatchedDN = matchedDN; public Exception Cause {
_serverMessage = serverMsg; get;
} }
/// <summary> /// <summary>
/// Returns the error message from the Ldap server, if this message is /// Returns the result code from the exception.
/// available (that is, if this message was set). If the message was not set, /// The codes are defined as <c>public final static int</c> members
/// this method returns null. /// of the Ldap Exception class. If the exception is a
/// </summary> /// result of error information returned from a directory operation, the
/// <returns> /// code will be one of those defined for the class. Otherwise, a local error
/// The error message or null if the message was not set. /// code is returned.
/// </returns> /// </summary>
public string LdapErrorMessage => /// <value>
_serverMessage != null && _serverMessage.Length == 0 ? null : _serverMessage; /// The result code.
/// </value>
/// <summary> public LdapStatusCode ResultCode {
/// Returns the lower level Exception which caused the failure, if any. get;
/// For example, an IOException with additional information may be returned }
/// on a CONNECT_ERROR failure.
/// </summary> /// <summary>
/// <value> /// Returns the part of a submitted distinguished name which could be
/// The cause. /// matched by the server.
/// </value> /// If the exception was caused by a local error, such as no server
public Exception Cause { get; } /// available, the return value is null. If the exception resulted from
/// an operation being executed on a server, the value is an empty string
/// <summary> /// except when the result of the operation was one of the following:.
/// Returns the result code from the exception. /// <ul><li>NO_SUCH_OBJECT</li><li>ALIAS_PROBLEM</li><li>INVALID_DN_SYNTAX</li><li>ALIAS_DEREFERENCING_PROBLEM</li></ul>
/// The codes are defined as <c>public final static int</c> members /// </summary>
/// of the Ldap Exception class. If the exception is a /// <value>
/// result of error information returned from a directory operation, the /// The matched dn.
/// code will be one of those defined for the class. Otherwise, a local error /// </value>
/// code is returned. public String MatchedDN {
/// </summary> get;
/// <value> }
/// The result code.
/// </value> /// <inheritdoc />
public LdapStatusCode ResultCode { get; } public override String Message => this.ResultCode.ToString().Humanize();
/// <summary> /// <inheritdoc />
/// Returns the part of a submitted distinguished name which could be public override String ToString() {
/// matched by the server. // Craft a string from the resource file
/// If the exception was caused by a local error, such as no server String msg = $"{nameof(LdapException)}: {base.Message} ({this.ResultCode}) {this.ResultCode.ToString().Humanize()}";
/// available, the return value is null. If the exception resulted from
/// an operation being executed on a server, the value is an empty string // Add server message
/// except when the result of the operation was one of the following:. if(!String.IsNullOrEmpty(this._serverMessage)) {
/// <ul><li>NO_SUCH_OBJECT</li><li>ALIAS_PROBLEM</li><li>INVALID_DN_SYNTAX</li><li>ALIAS_DEREFERENCING_PROBLEM</li></ul> msg += $"\r\nServer Message: {this._serverMessage}";
/// </summary> }
/// <value>
/// The matched dn. // Add Matched DN message
/// </value> if(this.MatchedDN != null) {
public string MatchedDN { get; } msg += $"\r\nMatched DN: {this.MatchedDN}";
}
/// <inheritdoc />
public override string Message => ResultCode.ToString().Humanize(); if(this.Cause != null) {
msg += $"\r\n{this.Cause}";
/// <inheritdoc /> }
public override string ToString()
{ return msg;
// Craft a string from the resource file }
var msg = $"{nameof(LdapException)}: {base.Message} ({ResultCode}) {ResultCode.ToString().Humanize()}"; }
// Add server message
if (!string.IsNullOrEmpty(_serverMessage))
{
msg += $"\r\nServer Message: {_serverMessage}";
}
// Add Matched DN message
if (MatchedDN != null)
{
msg += $"\r\nMatched DN: {MatchedDN}";
}
if (Cause != null)
{
msg += $"\r\n{Cause}";
}
return msg;
}
}
} }

View File

@ -1,55 +1,51 @@
#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>
/// Extension methods.
/// </summary>
public static class SmtpExtensions {
/// <summary> /// <summary>
/// Extension methods. /// The raw contents of this MailMessage as a MemoryStream.
/// </summary> /// </summary>
public static class SmtpExtensions /// <param name="self">The caller.</param>
{ /// <returns>A MemoryStream with the raw contents of this MailMessage.</returns>
/// <summary> public static MemoryStream ToMimeMessage(this MailMessage self) {
/// The raw contents of this MailMessage as a MemoryStream. if(self == null) {
/// </summary> throw new ArgumentNullException(nameof(self));
/// <param name="self">The caller.</param> }
/// <returns>A MemoryStream with the raw contents of this MailMessage.</returns>
public static MemoryStream ToMimeMessage(this MailMessage self) MemoryStream result = new MemoryStream();
{ Object mailWriter = MimeMessageConstants.MailWriterConstructor.Invoke(new Object[] { result });
if (self == null) _ = MimeMessageConstants.SendMethod.Invoke(
throw new ArgumentNullException(nameof(self)); self,
MimeMessageConstants.PrivateInstanceFlags,
var result = new MemoryStream(); null,
var mailWriter = MimeMessageConstants.MailWriterConstructor.Invoke(new object[] { result }); MimeMessageConstants.IsRunningInDotNetFourPointFive ? new[] { mailWriter, true, true } : new[] { mailWriter, true },
MimeMessageConstants.SendMethod.Invoke( null);
self,
MimeMessageConstants.PrivateInstanceFlags, result = new MemoryStream(result.ToArray());
null, _ = MimeMessageConstants.CloseMethod.Invoke(
MimeMessageConstants.IsRunningInDotNetFourPointFive ? new[] { mailWriter, true, true } : new[] { mailWriter, true }, mailWriter,
null); MimeMessageConstants.PrivateInstanceFlags,
null,
result = new MemoryStream(result.ToArray()); new Object[] { },
MimeMessageConstants.CloseMethod.Invoke( null);
mailWriter, result.Position = 0;
MimeMessageConstants.PrivateInstanceFlags, return result;
null, }
new object[] { },
null); internal static class MimeMessageConstants {
result.Position = 0; public static readonly BindingFlags PrivateInstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;
return result; 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 MethodInfo CloseMethod = MailWriter.GetMethod("Close", PrivateInstanceFlags);
internal static class MimeMessageConstants public static readonly MethodInfo SendMethod = typeof(MailMessage).GetMethod("Send", PrivateInstanceFlags);
{ public static readonly Boolean IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3;
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 ConstructorInfo MailWriterConstructor = MailWriter.GetConstructor(PrivateInstanceFlags, null, new[] { typeof(Stream) }, null);
public static readonly MethodInfo CloseMethod = MailWriter.GetMethod("Close", PrivateInstanceFlags);
public static readonly MethodInfo SendMethod = typeof(MailMessage).GetMethod("Send", PrivateInstanceFlags);
public static readonly bool IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3;
}
}
} }
#endif #endif

View File

@ -1,58 +1,58 @@
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>
/// Provides various extension methods for networking-related tasks.
/// </summary>
public static class NetworkExtensions {
/// <summary> /// <summary>
/// Provides various extension methods for networking-related tasks. /// Determines whether the IP address is private.
/// </summary> /// </summary>
public static class NetworkExtensions /// <param name="address">The IP address.</param>
{ /// <returns>
/// <summary> /// True if the IP Address is private; otherwise, false.
/// Determines whether the IP address is private. /// </returns>
/// </summary> /// <exception cref="ArgumentNullException">address.</exception>
/// <param name="address">The IP address.</param> public static Boolean IsPrivateAddress(this IPAddress address) {
/// <returns> if(address == null) {
/// True if the IP Address is private; otherwise, false. throw new ArgumentNullException(nameof(address));
/// </returns> }
/// <exception cref="ArgumentNullException">address.</exception>
public static bool IsPrivateAddress(this IPAddress address) Byte[] octets = address.ToString().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).Select(Byte.Parse).ToArray();
{ Boolean is24Bit = octets[0] == 10;
if (address == null) Boolean is20Bit = octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31;
throw new ArgumentNullException(nameof(address)); Boolean is16Bit = octets[0] == 192 && octets[1] == 168;
var octets = address.ToString().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).Select(byte.Parse).ToArray(); return is24Bit || is20Bit || is16Bit;
var is24Bit = octets[0] == 10; }
var is20Bit = octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31);
var is16Bit = octets[0] == 192 && octets[1] == 168; /// <summary>
/// Converts an IPv4 Address to its Unsigned, 32-bit integer representation.
return is24Bit || is20Bit || is16Bit; /// </summary>
} /// <param name="address">The address.</param>
/// <returns>
/// <summary> /// A 32-bit unsigned integer converted from four bytes at a specified position in a byte array.
/// Converts an IPv4 Address to its Unsigned, 32-bit integer representation. /// </returns>
/// </summary> /// <exception cref="ArgumentNullException">address.</exception>
/// <param name="address">The address.</param> /// <exception cref="ArgumentException">InterNetwork - address.</exception>
/// <returns> public static UInt32 ToUInt32(this IPAddress address) {
/// A 32-bit unsigned integer converted from four bytes at a specified position in a byte array. if(address == null) {
/// </returns> throw new ArgumentNullException(nameof(address));
/// <exception cref="ArgumentNullException">address.</exception> }
/// <exception cref="ArgumentException">InterNetwork - address.</exception>
public static uint ToUInt32(this IPAddress address) if(address.AddressFamily != AddressFamily.InterNetwork) {
{ throw new ArgumentException($"Address has to be of family '{nameof(AddressFamily.InterNetwork)}'", nameof(address));
if (address == null) }
throw new ArgumentNullException(nameof(address));
Byte[] addressBytes = address.GetAddressBytes();
if (address.AddressFamily != AddressFamily.InterNetwork) if(BitConverter.IsLittleEndian) {
throw new ArgumentException($"Address has to be of family '{nameof(AddressFamily.InterNetwork)}'", nameof(address)); Array.Reverse(addressBytes);
}
var addressBytes = address.GetAddressBytes();
if (BitConverter.IsLittleEndian) return BitConverter.ToUInt32(addressBytes, 0);
Array.Reverse(addressBytes); }
}
return BitConverter.ToUInt32(addressBytes, 0);
}
}
} }

View File

@ -1,80 +1,75 @@
#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>
/// Extension methods.
/// </summary>
public static class WindowsServicesExtensions {
/// <summary> /// <summary>
/// Extension methods. /// Runs a service in console mode.
/// </summary> /// </summary>
public static class WindowsServicesExtensions /// <param name="serviceToRun">The service to run.</param>
{ public static void RunInConsoleMode(this ServiceBase serviceToRun) {
/// <summary> if(serviceToRun == null) {
/// Runs a service in console mode. throw new ArgumentNullException(nameof(serviceToRun));
/// </summary> }
/// <param name="serviceToRun">The service to run.</param>
public static void RunInConsoleMode(this ServiceBase serviceToRun) RunInConsoleMode(new[] { serviceToRun });
{ }
if (serviceToRun == null)
throw new ArgumentNullException(nameof(serviceToRun)); /// <summary>
/// Runs a set of services in console mode.
RunInConsoleMode(new[] { serviceToRun }); /// </summary>
} /// <param name="servicesToRun">The services to run.</param>
public static void RunInConsoleMode(this ServiceBase[] servicesToRun) {
/// <summary> if(servicesToRun == null) {
/// Runs a set of services in console mode. throw new ArgumentNullException(nameof(servicesToRun));
/// </summary> }
/// <param name="servicesToRun">The services to run.</param>
public static void RunInConsoleMode(this ServiceBase[] servicesToRun) const String onStartMethodName = "OnStart";
{ const String onStopMethodName = "OnStop";
if (servicesToRun == null)
throw new ArgumentNullException(nameof(servicesToRun)); MethodInfo onStartMethod = typeof(ServiceBase).GetMethod(onStartMethodName,
BindingFlags.Instance | BindingFlags.NonPublic);
const string onStartMethodName = "OnStart"; MethodInfo onStopMethod = typeof(ServiceBase).GetMethod(onStopMethodName,
const string onStopMethodName = "OnStop"; BindingFlags.Instance | BindingFlags.NonPublic);
var onStartMethod = typeof(ServiceBase).GetMethod(onStartMethodName, List<Thread> serviceThreads = new List<Thread>();
BindingFlags.Instance | BindingFlags.NonPublic); "Starting services . . .".Info(Runtime.EntryAssemblyName.Name);
var onStopMethod = typeof(ServiceBase).GetMethod(onStopMethodName,
BindingFlags.Instance | BindingFlags.NonPublic); foreach(ServiceBase service in servicesToRun) {
Thread thread = new Thread(() => {
var serviceThreads = new List<Thread>(); _ = onStartMethod.Invoke(service, new Object[] { new String[] { } });
"Starting services . . .".Info(Runtime.EntryAssemblyName.Name); $"Started service '{service.GetType().Name}'".Info(service.GetType());
});
foreach (var service in servicesToRun)
{ serviceThreads.Add(thread);
var thread = new Thread(() => thread.Start();
{ }
onStartMethod.Invoke(service, new object[] { new string[] { } });
$"Started service '{service.GetType().Name}'".Info(service.GetType()); "Press any key to stop all services.".Info(Runtime.EntryAssemblyName.Name);
}); _ = Terminal.ReadKey(true, true);
"Stopping services . . .".Info(Runtime.EntryAssemblyName.Name);
serviceThreads.Add(thread);
thread.Start(); foreach(ServiceBase service in servicesToRun) {
} _ = onStopMethod.Invoke(service, null);
$"Stopped service '{service.GetType().Name}'".Info(service.GetType());
"Press any key to stop all services.".Info(Runtime.EntryAssemblyName.Name); }
Terminal.ReadKey(true, true);
"Stopping services . . .".Info(Runtime.EntryAssemblyName.Name); foreach(Thread thread in serviceThreads) {
thread.Join();
foreach (var service in servicesToRun) }
{
onStopMethod.Invoke(service, null); "Stopped all services.".Info(Runtime.EntryAssemblyName.Name);
$"Stopped service '{service.GetType().Name}'".Info(service.GetType()); }
} }
foreach (var thread in serviceThreads)
thread.Join();
"Stopped all services.".Info(Runtime.EntryAssemblyName.Name);
}
}
} }
#endif #endif

View File

@ -1,179 +1,184 @@
#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>
/// Represents a buffer of bytes containing pixels in BGRA byte order
/// loaded from an image that is passed on to the constructor.
/// Data contains all the raw bytes (without scanline left-over bytes)
/// where they can be quickly changed and then a new bitmap
/// can be created from the byte data.
/// </summary>
public class BitmapBuffer {
/// <summary> /// <summary>
/// Represents a buffer of bytes containing pixels in BGRA byte order /// A constant representing the number of
/// loaded from an image that is passed on to the constructor. /// bytes per pixel in the pixel data. This is
/// Data contains all the raw bytes (without scanline left-over bytes) /// always 4 but it is kept here for readability.
/// where they can be quickly changed and then a new bitmap
/// can be created from the byte data.
/// </summary> /// </summary>
public class BitmapBuffer public const Int32 BytesPerPixel = 4;
{
/// <summary> /// <summary>
/// A constant representing the number of /// The blue byte offset within a pixel offset. This is 0.
/// bytes per pixel in the pixel data. This is /// </summary>
/// always 4 but it is kept here for readability. public const Int32 BOffset = 0;
/// </summary>
public const int BytesPerPixel = 4; /// <summary>
/// The green byte offset within a pixel offset. This is 1.
/// <summary> /// </summary>
/// The blue byte offset within a pixel offset. This is 0. public const Int32 GOffset = 1;
/// </summary>
public const int BOffset = 0; /// <summary>
/// The red byte offset within a pixel offset. This is 2.
/// <summary> /// </summary>
/// The green byte offset within a pixel offset. This is 1. public const Int32 ROffset = 2;
/// </summary>
public const int GOffset = 1; /// <summary>
/// The alpha byte offset within a pixel offset. This is 3.
/// <summary> /// </summary>
/// The red byte offset within a pixel offset. This is 2. public const Int32 AOffset = 3;
/// </summary>
public const int ROffset = 2; /// <summary>
/// Initializes a new instance of the <see cref="BitmapBuffer"/> class.
/// <summary> /// Data will not contain left-over stride bytes
/// The alpha byte offset within a pixel offset. This is 3. /// </summary>
/// </summary> /// <param name="sourceImage">The source image.</param>
public const int AOffset = 3; public BitmapBuffer(Image sourceImage) {
// Acquire or create the source bitmap in a manageable format
/// <summary> Boolean disposeSourceBitmap = false;
/// Initializes a new instance of the <see cref="BitmapBuffer"/> class. if(!(sourceImage is Bitmap sourceBitmap) || sourceBitmap.PixelFormat != this.PixelFormat) {
/// Data will not contain left-over stride bytes sourceBitmap = new Bitmap(sourceImage.Width, sourceImage.Height, this.PixelFormat);
/// </summary> using(Graphics g = Graphics.FromImage(sourceBitmap)) {
/// <param name="sourceImage">The source image.</param> g.DrawImage(sourceImage, 0, 0);
public BitmapBuffer(Image sourceImage) }
{
// Acquire or create the source bitmap in a manageable format // We created this bitmap. Make sure we clear it from memory
var disposeSourceBitmap = false; disposeSourceBitmap = true;
if (!(sourceImage is Bitmap sourceBitmap) || sourceBitmap.PixelFormat != PixelFormat) }
{
sourceBitmap = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat); // Lock the bits
using (var g = Graphics.FromImage(sourceBitmap)) BitmapData sourceDataLocker = sourceBitmap.LockBits(
{ new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
g.DrawImage(sourceImage, 0, 0); ImageLockMode.ReadOnly,
} sourceBitmap.PixelFormat);
// We created this bitmap. Make sure we clear it from memory // Set basic properties
disposeSourceBitmap = true; this.ImageWidth = sourceBitmap.Width;
} this.ImageHeight = sourceBitmap.Height;
this.LineStride = sourceDataLocker.Stride;
// Lock the bits
var sourceDataLocker = sourceBitmap.LockBits( // State variables
new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), this.LineLength = sourceBitmap.Width * BytesPerPixel; // may or may not be equal to the Stride
ImageLockMode.ReadOnly, this.Data = new Byte[this.LineLength * sourceBitmap.Height];
sourceBitmap.PixelFormat);
// copy line by line in order to ignore the useless left-over stride
// Set basic properties _ = Parallel.For(0, sourceBitmap.Height, y => {
ImageWidth = sourceBitmap.Width; IntPtr sourceAddress = sourceDataLocker.Scan0 + sourceDataLocker.Stride * y;
ImageHeight = sourceBitmap.Height; Int32 targetAddress = y * this.LineLength;
LineStride = sourceDataLocker.Stride; Marshal.Copy(sourceAddress, this.Data, targetAddress, this.LineLength);
});
// State variables
LineLength = sourceBitmap.Width * BytesPerPixel; // may or may not be equal to the Stride // finally unlock the bitmap
Data = new byte[LineLength * sourceBitmap.Height]; sourceBitmap.UnlockBits(sourceDataLocker);
// copy line by line in order to ignore the useless left-over stride // dispose the source bitmap if we had to create it
Parallel.For(0, sourceBitmap.Height, y => if(disposeSourceBitmap) {
{ sourceBitmap.Dispose();
var sourceAddress = sourceDataLocker.Scan0 + (sourceDataLocker.Stride * y); }
var targetAddress = y * LineLength; }
Marshal.Copy(sourceAddress, Data, targetAddress, LineLength);
}); /// <summary>
/// Contains all the bytes of the pixel data
// finally unlock the bitmap /// Each horizontal scanline is represented by LineLength
sourceBitmap.UnlockBits(sourceDataLocker); /// rather than by LinceStride. The left-over stride bytes
/// are removed.
// dispose the source bitmap if we had to create it /// </summary>
if (disposeSourceBitmap) public Byte[] Data {
{ get;
sourceBitmap.Dispose(); }
}
} /// <summary>
/// Gets the width of the image.
/// <summary> /// </summary>
/// Contains all the bytes of the pixel data public Int32 ImageWidth {
/// Each horizontal scanline is represented by LineLength get;
/// rather than by LinceStride. The left-over stride bytes }
/// are removed.
/// </summary> /// <summary>
public byte[] Data { get; } /// Gets the height of the image.
/// </summary>
/// <summary> public Int32 ImageHeight {
/// Gets the width of the image. get;
/// </summary> }
public int ImageWidth { get; }
/// <summary>
/// <summary> /// Gets the pixel format. This will always be Format32bppArgb.
/// Gets the height of the image. /// </summary>
/// </summary> public PixelFormat PixelFormat { get; } = PixelFormat.Format32bppArgb;
public int ImageHeight { get; }
/// <summary>
/// <summary> /// Gets the length in bytes of a line of pixel data.
/// Gets the pixel format. This will always be Format32bppArgb. /// Basically the same as Line Length except Stride might be a little larger as
/// </summary> /// some bitmaps might be DWORD-algned.
public PixelFormat PixelFormat { get; } = PixelFormat.Format32bppArgb; /// </summary>
public Int32 LineStride {
/// <summary> get;
/// Gets the length in bytes of a line of pixel data. }
/// Basically the same as Line Length except Stride might be a little larger as
/// some bitmaps might be DWORD-algned. /// <summary>
/// </summary> /// Gets the length in bytes of a line of pixel data.
public int LineStride { get; } /// Basically the same as Stride except Stride might be a little larger as
/// some bitmaps might be DWORD-algned.
/// <summary> /// </summary>
/// Gets the length in bytes of a line of pixel data. public Int32 LineLength {
/// Basically the same as Stride except Stride might be a little larger as get;
/// some bitmaps might be DWORD-algned. }
/// </summary>
public int LineLength { get; } /// <summary>
/// Gets the index of the first byte in the BGRA pixel data for the given image coordinates.
/// <summary> /// </summary>
/// Gets the index of the first byte in the BGRA pixel data for the given image coordinates. /// <param name="x">The x.</param>
/// </summary> /// <param name="y">The y.</param>
/// <param name="x">The x.</param> /// <returns>Index of the first byte in the BGRA pixel.</returns>
/// <param name="y">The y.</param> /// <exception cref="ArgumentOutOfRangeException">
/// <returns>Index of the first byte in the BGRA pixel.</returns> /// x
/// <exception cref="ArgumentOutOfRangeException"> /// or
/// x /// y.
/// or /// </exception>
/// y. public Int32 GetPixelOffset(Int32 x, Int32 y) {
/// </exception> if(x < 0 || x > this.ImageWidth) {
public int GetPixelOffset(int x, int y) throw new ArgumentOutOfRangeException(nameof(x));
{ }
if (x < 0 || x > ImageWidth) throw new ArgumentOutOfRangeException(nameof(x));
if (y < 0 || y > ImageHeight) throw new ArgumentOutOfRangeException(nameof(y)); if(y < 0 || y > this.ImageHeight) {
throw new ArgumentOutOfRangeException(nameof(y));
return (y * LineLength) + (x * BytesPerPixel); }
}
return y * this.LineLength + x * BytesPerPixel;
/// <summary> }
/// Converts the pixel data bytes held in the buffer
/// to a 32-bit RGBA bitmap. /// <summary>
/// </summary> /// Converts the pixel data bytes held in the buffer
/// <returns>Pixel data for a graphics image and its attribute.</returns> /// to a 32-bit RGBA bitmap.
public Bitmap ToBitmap() /// </summary>
{ /// <returns>Pixel data for a graphics image and its attribute.</returns>
var bitmap = new Bitmap(ImageWidth, ImageHeight, PixelFormat); public Bitmap ToBitmap() {
var bitLocker = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat); Bitmap bitmap = new Bitmap(this.ImageWidth, this.ImageHeight, this.PixelFormat);
BitmapData 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 => {
var sourceOffset = GetPixelOffset(0, y); Int32 sourceOffset = this.GetPixelOffset(0, y);
var targetOffset = bitLocker.Scan0 + (y * bitLocker.Stride); IntPtr targetOffset = bitLocker.Scan0 + y * bitLocker.Stride;
Marshal.Copy(Data, sourceOffset, targetOffset, bitLocker.Width); Marshal.Copy(this.Data, sourceOffset, targetOffset, bitLocker.Width);
}); });
bitmap.UnlockBits(bitLocker); bitmap.UnlockBits(bitLocker);
return bitmap; return bitmap;
} }
} }
} }
#endif #endif

View File

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

View File

@ -1,450 +1,414 @@
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>
/// Provides miscellaneous network utilities such as a Public IP finder,
/// a DNS client to query DNS records of any kind, and an NTP client.
/// </summary>
public static class Network {
/// <summary> /// <summary>
/// Provides miscellaneous network utilities such as a Public IP finder, /// The DNS default port.
/// a DNS client to query DNS records of any kind, and an NTP client.
/// </summary> /// </summary>
public static class Network public const Int32 DnsDefaultPort = 53;
{
/// <summary> /// <summary>
/// The DNS default port. /// The NTP default port.
/// </summary> /// </summary>
public const int DnsDefaultPort = 53; public const Int32 NtpDefaultPort = 123;
/// <summary> /// <summary>
/// The NTP default port. /// Gets the name of the host.
/// </summary> /// </summary>
public const int NtpDefaultPort = 123; /// <value>
/// The name of the host.
/// <summary> /// </value>
/// Gets the name of the host. public static String HostName => IPGlobalProperties.GetIPGlobalProperties().HostName;
/// </summary>
/// <value> /// <summary>
/// The name of the host. /// Gets the name of the network domain.
/// </value> /// </summary>
public static string HostName => IPGlobalProperties.GetIPGlobalProperties().HostName; /// <value>
/// The name of the network domain.
/// <summary> /// </value>
/// Gets the name of the network domain. public static String DomainName => IPGlobalProperties.GetIPGlobalProperties().DomainName;
/// </summary>
/// <value> #region IP Addresses and Adapters Information Methods
/// The name of the network domain.
/// </value> /// <summary>
public static string DomainName => IPGlobalProperties.GetIPGlobalProperties().DomainName; /// Gets the active IPv4 interfaces.
/// Only those interfaces with a valid unicast address and a valid gateway will be returned in the collection.
#region IP Addresses and Adapters Information Methods /// </summary>
/// <returns>
/// <summary> /// A collection of NetworkInterface/IPInterfaceProperties pairs
/// Gets the active IPv4 interfaces. /// that represents the active IPv4 interfaces.
/// Only those interfaces with a valid unicast address and a valid gateway will be returned in the collection. /// </returns>
/// </summary> public static Dictionary<NetworkInterface, IPInterfaceProperties> GetIPv4Interfaces() {
/// <returns> // zero conf ip address
/// A collection of NetworkInterface/IPInterfaceProperties pairs IPAddress zeroConf = new IPAddress(0);
/// that represents the active IPv4 interfaces.
/// </returns> NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces()
public static Dictionary<NetworkInterface, IPInterfaceProperties> GetIPv4Interfaces() .Where(network =>
{ network.OperationalStatus == OperationalStatus.Up
// zero conf ip address && network.NetworkInterfaceType != NetworkInterfaceType.Unknown
var zeroConf = new IPAddress(0); && network.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.ToArray();
var adapters = NetworkInterface.GetAllNetworkInterfaces()
.Where(network => Dictionary<NetworkInterface, IPInterfaceProperties> result = new Dictionary<NetworkInterface, IPInterfaceProperties>();
network.OperationalStatus == OperationalStatus.Up
&& network.NetworkInterfaceType != NetworkInterfaceType.Unknown foreach(NetworkInterface adapter in adapters) {
&& network.NetworkInterfaceType != NetworkInterfaceType.Loopback) IPInterfaceProperties properties = adapter.GetIPProperties();
.ToArray(); if(properties == null
|| properties.GatewayAddresses.Count == 0
var result = new Dictionary<NetworkInterface, IPInterfaceProperties>(); || properties.GatewayAddresses.All(gateway => Equals(gateway.Address, zeroConf))
|| properties.UnicastAddresses.Count == 0
foreach (var adapter in adapters) || properties.GatewayAddresses.All(address => Equals(address.Address, zeroConf))
{ || properties.UnicastAddresses.Any(a => a.Address.AddressFamily == AddressFamily.InterNetwork) ==
var properties = adapter.GetIPProperties(); false) {
if (properties == null continue;
|| properties.GatewayAddresses.Count == 0 }
|| properties.GatewayAddresses.All(gateway => Equals(gateway.Address, zeroConf))
|| properties.UnicastAddresses.Count == 0 result[adapter] = properties;
|| properties.GatewayAddresses.All(address => Equals(address.Address, zeroConf)) }
|| properties.UnicastAddresses.Any(a => a.Address.AddressFamily == AddressFamily.InterNetwork) ==
false) return result;
continue; }
result[adapter] = properties; /// <summary>
} /// Retrieves the local ip addresses.
/// </summary>
return result; /// <param name="includeLoopback">if set to <c>true</c> [include loopback].</param>
} /// <returns>An array of local ip addresses.</returns>
public static IPAddress[] GetIPv4Addresses(Boolean includeLoopback = true) =>
/// <summary> GetIPv4Addresses(NetworkInterfaceType.Unknown, true, includeLoopback);
/// Retrieves the local ip addresses.
/// </summary> /// <summary>
/// <param name="includeLoopback">if set to <c>true</c> [include loopback].</param> /// Retrieves the local ip addresses.
/// <returns>An array of local ip addresses.</returns> /// </summary>
public static IPAddress[] GetIPv4Addresses(bool includeLoopback = true) => /// <param name="interfaceType">Type of the interface.</param>
GetIPv4Addresses(NetworkInterfaceType.Unknown, true, includeLoopback); /// <param name="skipTypeFilter">if set to <c>true</c> [skip type filter].</param>
/// <param name="includeLoopback">if set to <c>true</c> [include loopback].</param>
/// <summary> /// <returns>An array of local ip addresses.</returns>
/// Retrieves the local ip addresses. public static IPAddress[] GetIPv4Addresses(
/// </summary> NetworkInterfaceType interfaceType,
/// <param name="interfaceType">Type of the interface.</param> Boolean skipTypeFilter = false,
/// <param name="skipTypeFilter">if set to <c>true</c> [skip type filter].</param> Boolean includeLoopback = false) {
/// <param name="includeLoopback">if set to <c>true</c> [include loopback].</param> List<IPAddress> addressList = new List<IPAddress>();
/// <returns>An array of local ip addresses.</returns> NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces()
public static IPAddress[] GetIPv4Addresses( .Where(ni =>
NetworkInterfaceType interfaceType,
bool skipTypeFilter = false,
bool includeLoopback = false)
{
var addressList = new List<IPAddress>();
var interfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni =>
#if NET452 #if NET452
ni.IsReceiveOnly == false && ni.IsReceiveOnly == false &&
#endif #endif
(skipTypeFilter || ni.NetworkInterfaceType == interfaceType) && (skipTypeFilter || ni.NetworkInterfaceType == interfaceType) &&
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();
}
/// <summary>
/// Gets the public IP address using ipify.org. /// <summary>
/// </summary> /// Gets the public IP address using ipify.org.
/// <param name="ct">The cancellation token.</param> /// </summary>
/// <returns>A public IP address of the result produced by this Task.</returns> /// <param name="ct">The cancellation token.</param>
public static async Task<IPAddress> GetPublicIPAddressAsync(CancellationToken ct = default) /// <returns>A public IP address of the result produced by this Task.</returns>
{ public static async Task<IPAddress> GetPublicIPAddressAsync(CancellationToken ct = default) {
using (var client = new HttpClient()) using(HttpClient 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)); }
} }
}
/// <summary>
/// <summary> /// Gets the public IP address using ipify.org.
/// Gets the public IP address using ipify.org. /// </summary>
/// </summary> /// <returns>A public ip address.</returns>
/// <returns>A public ip address.</returns> public static IPAddress GetPublicIPAddress() => GetPublicIPAddressAsync().GetAwaiter().GetResult();
public static IPAddress GetPublicIPAddress() => GetPublicIPAddressAsync().GetAwaiter().GetResult();
/// <summary>
/// <summary> /// Gets the configured IPv4 DNS servers for the active network interfaces.
/// Gets the configured IPv4 DNS servers for the active network interfaces. /// </summary>
/// </summary> /// <returns>
/// <returns> /// 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 IPAddress[] GetIPv4DnsServers()
public static IPAddress[] GetIPv4DnsServers() => GetIPv4Interfaces()
=> GetIPv4Interfaces() .Select(a => a.Value.DnsAddresses.Where(d => d.AddressFamily == AddressFamily.InterNetwork))
.Select(a => a.Value.DnsAddresses.Where(d => d.AddressFamily == AddressFamily.InterNetwork)) .SelectMany(d => d)
.SelectMany(d => d) .ToArray();
.ToArray();
#endregion
#endregion
#region DNS and NTP Clients
#region DNS and NTP Clients
/// <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. /// </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");
{ return GetDnsHostEntry(fqdn, dnsServer, DnsDefaultPort);
var dnsServer = GetIPv4DnsServers().FirstOrDefault() ?? IPAddress.Parse("8.8.8.8"); }
return GetDnsHostEntry(fqdn, dnsServer, DnsDefaultPort);
} /// <summary>
/// Gets the DNS host entry (a list of IP addresses) for the domain name.
/// <summary> /// </summary>
/// Gets the DNS host entry (a list of IP addresses) for the domain name. /// <param name="fqdn">The FQDN.</param>
/// </summary> /// <param name="ct">The cancellation token.</param>
/// <param name="fqdn">The FQDN.</param> /// <returns>An array of local ip addresses of the result produced by this task.</returns>
/// <param name="ct">The cancellation token.</param> public static Task<IPAddress[]> GetDnsHostEntryAsync(String fqdn,
/// <returns>An array of local ip addresses of the result produced by this task.</returns> CancellationToken ct = default) => Task.Run(() => GetDnsHostEntry(fqdn), ct);
public static Task<IPAddress[]> GetDnsHostEntryAsync(string fqdn,
CancellationToken ct = default) /// <summary>
{ /// Gets the DNS host entry (a list of IP addresses) for the domain name.
return Task.Run(() => GetDnsHostEntry(fqdn), ct); /// </summary>
} /// <param name="fqdn">The FQDN.</param>
/// <param name="dnsServer">The DNS server.</param>
/// <summary> /// <param name="port">The port.</param>
/// Gets the DNS host entry (a list of IP addresses) for the domain name. /// <returns>
/// </summary> /// An array of local ip addresses.
/// <param name="fqdn">The FQDN.</param> /// </returns>
/// <param name="dnsServer">The DNS server.</param> /// <exception cref="ArgumentNullException">fqdn.</exception>
/// <param name="port">The port.</param> public static IPAddress[] GetDnsHostEntry(String fqdn, IPAddress dnsServer, Int32 port) {
/// <returns> if(fqdn == null) {
/// An array of local ip addresses. throw new ArgumentNullException(nameof(fqdn));
/// </returns> }
/// <exception cref="ArgumentNullException">fqdn.</exception>
public static IPAddress[] GetDnsHostEntry(string fqdn, IPAddress dnsServer, int port) if(fqdn.IndexOf(".", StringComparison.Ordinal) == -1) {
{ fqdn += "." + IPGlobalProperties.GetIPGlobalProperties().DomainName;
if (fqdn == null) }
throw new ArgumentNullException(nameof(fqdn));
while(true) {
if (fqdn.IndexOf(".", StringComparison.Ordinal) == -1) if(fqdn.EndsWith(".") == false) {
{ break;
fqdn += "." + IPGlobalProperties.GetIPGlobalProperties().DomainName; }
}
fqdn = fqdn.Substring(0, fqdn.Length - 1);
while (true) }
{
if (fqdn.EndsWith(".") == false) break; DnsClient client = new DnsClient(dnsServer, port);
IList<IPAddress> result = client.Lookup(fqdn);
fqdn = fqdn.Substring(0, fqdn.Length - 1); return result.ToArray();
} }
var client = new DnsClient(dnsServer, port); /// <summary>
var result = client.Lookup(fqdn); /// Gets the DNS host entry (a list of IP addresses) for the domain name.
return result.ToArray(); /// </summary>
} /// <param name="fqdn">The FQDN.</param>
/// <param name="dnsServer">The DNS server.</param>
/// <summary> /// <param name="port">The port.</param>
/// Gets the DNS host entry (a list of IP addresses) for the domain name. /// <param name="ct">The cancellation token.</param>
/// </summary> /// <returns>An array of local ip addresses of the result produced by this task.</returns>
/// <param name="fqdn">The FQDN.</param> public static Task<IPAddress[]> GetDnsHostEntryAsync(String fqdn, IPAddress dnsServer, Int32 port, CancellationToken ct = default) => Task.Run(() => GetDnsHostEntry(fqdn, dnsServer, port), ct);
/// <param name="dnsServer">The DNS server.</param>
/// <param name="port">The port.</param> /// <summary>
/// <param name="ct">The cancellation token.</param> /// Gets the reverse lookup FQDN of the given IP Address.
/// <returns>An array of local ip addresses of the result produced by this task.</returns> /// </summary>
public static Task<IPAddress[]> GetDnsHostEntryAsync( /// <param name="query">The query.</param>
string fqdn, /// <param name="dnsServer">The DNS server.</param>
IPAddress dnsServer, /// <param name="port">The port.</param>
int port, /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
CancellationToken ct = default) public static String GetDnsPointerEntry(IPAddress query, IPAddress dnsServer, Int32 port) => new DnsClient(dnsServer, port).Reverse(query);
{
return Task.Run(() => GetDnsHostEntry(fqdn, dnsServer, port), ct); /// <summary>
} /// Gets the reverse lookup FQDN of the given IP Address.
/// </summary>
/// <summary> /// <param name="query">The query.</param>
/// Gets the reverse lookup FQDN of the given IP Address. /// <param name="dnsServer">The DNS server.</param>
/// </summary> /// <param name="port">The port.</param>
/// <param name="query">The query.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="dnsServer">The DNS server.</param> /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <param name="port">The port.</param> public static Task<String> GetDnsPointerEntryAsync(
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> IPAddress query,
public static string GetDnsPointerEntry(IPAddress query, IPAddress dnsServer, int port) => new DnsClient(dnsServer, port).Reverse(query); IPAddress dnsServer,
Int32 port,
/// <summary> CancellationToken ct = default) => Task.Run(() => GetDnsPointerEntry(query, dnsServer, port), ct);
/// Gets the reverse lookup FQDN of the given IP Address.
/// </summary> /// <summary>
/// <param name="query">The query.</param> /// Gets the reverse lookup FQDN of the given IP Address.
/// <param name="dnsServer">The DNS server.</param> /// </summary>
/// <param name="port">The port.</param> /// <param name="query">The query.</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 String GetDnsPointerEntry(IPAddress query) => new DnsClient(GetIPv4DnsServers().FirstOrDefault()).Reverse(query);
public static Task<string> GetDnsPointerEntryAsync(
IPAddress query, /// <summary>
IPAddress dnsServer, /// Gets the reverse lookup FQDN of the given IP Address.
int port, /// </summary>
CancellationToken ct = default) /// <param name="query">The query.</param>
{ /// <param name="ct">The cancellation token.</param>
return Task.Run(() => GetDnsPointerEntry(query, dnsServer, port), ct); /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
} public static Task<String> GetDnsPointerEntryAsync(
IPAddress query,
/// <summary> CancellationToken ct = default) => Task.Run(() => GetDnsPointerEntry(query), ct);
/// Gets the reverse lookup FQDN of the given IP Address.
/// </summary> /// <summary>
/// <param name="query">The query.</param> /// Queries the DNS server for the specified record type.
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> /// </summary>
public static string GetDnsPointerEntry(IPAddress query) => new DnsClient(GetIPv4DnsServers().FirstOrDefault()).Reverse(query); /// <param name="query">The query.</param>
/// <param name="recordType">Type of the record.</param>
/// <summary> /// <param name="dnsServer">The DNS server.</param>
/// Gets the reverse lookup FQDN of the given IP Address. /// <param name="port">The port.</param>
/// </summary> /// <returns>
/// <param name="query">The query.</param> /// Appropriate DNS server for the specified record type.
/// <param name="ct">The cancellation token.</param> /// </returns>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> public static DnsQueryResult QueryDns(String query, DnsRecordType recordType, IPAddress dnsServer, Int32 port) {
public static Task<string> GetDnsPointerEntryAsync( if(query == null) {
IPAddress query, throw new ArgumentNullException(nameof(query));
CancellationToken ct = default) }
{
return Task.Run(() => GetDnsPointerEntry(query), ct); DnsClient.DnsClientResponse response = new DnsClient(dnsServer, port).Resolve(query, recordType);
} return new DnsQueryResult(response);
}
/// <summary>
/// Queries the DNS server for the specified record type. /// <summary>
/// </summary> /// Queries the DNS server for the specified record type.
/// <param name="query">The query.</param> /// </summary>
/// <param name="recordType">Type of the record.</param> /// <param name="query">The query.</param>
/// <param name="dnsServer">The DNS server.</param> /// <param name="recordType">Type of the record.</param>
/// <param name="port">The port.</param> /// <param name="dnsServer">The DNS server.</param>
/// <returns> /// <param name="port">The port.</param>
/// Appropriate DNS server for the specified record type. /// <param name="ct">The cancellation token.</param>
/// </returns> /// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns>
public static DnsQueryResult QueryDns(string query, DnsRecordType recordType, IPAddress dnsServer, int port) public static Task<DnsQueryResult> QueryDnsAsync(
{ String query,
if (query == null) DnsRecordType recordType,
throw new ArgumentNullException(nameof(query)); IPAddress dnsServer,
Int32 port,
var response = new DnsClient(dnsServer, port).Resolve(query, recordType); CancellationToken ct = default) => Task.Run(() => QueryDns(query, recordType, dnsServer, port), ct);
return new DnsQueryResult(response);
} /// <summary>
/// Queries the DNS server for the specified record type.
/// <summary> /// </summary>
/// Queries the DNS server for the specified record type. /// <param name="query">The query.</param>
/// </summary> /// <param name="recordType">Type of the record.</param>
/// <param name="query">The query.</param> /// <returns>Appropriate DNS server for the specified record type.</returns>
/// <param name="recordType">Type of the record.</param> public static DnsQueryResult QueryDns(String query, DnsRecordType recordType) => QueryDns(query, recordType, GetIPv4DnsServers().FirstOrDefault(), DnsDefaultPort);
/// <param name="dnsServer">The DNS server.</param>
/// <param name="port">The port.</param> /// <summary>
/// <param name="ct">The cancellation token.</param> /// Queries the DNS server for the specified record type.
/// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns> /// </summary>
public static Task<DnsQueryResult> QueryDnsAsync( /// <param name="query">The query.</param>
string query, /// <param name="recordType">Type of the record.</param>
DnsRecordType recordType, /// <param name="ct">The cancellation token.</param>
IPAddress dnsServer, /// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns>
int port, public static Task<DnsQueryResult> QueryDnsAsync(
CancellationToken ct = default) String query,
{ DnsRecordType recordType,
return Task.Run(() => QueryDns(query, recordType, dnsServer, port), ct); CancellationToken ct = default) => Task.Run(() => QueryDns(query, recordType), ct);
}
/// <summary>
/// <summary> /// Gets the UTC time by querying from an NTP server.
/// Queries the DNS server for the specified record type. /// </summary>
/// </summary> /// <param name="ntpServerAddress">The NTP server address.</param>
/// <param name="query">The query.</param> /// <param name="port">The port.</param>
/// <param name="recordType">Type of the record.</param> /// <returns>
/// <returns>Appropriate DNS server for the specified record type.</returns> /// A new instance of the DateTime structure to
public static DnsQueryResult QueryDns(string query, DnsRecordType recordType) => QueryDns(query, recordType, GetIPv4DnsServers().FirstOrDefault(), DnsDefaultPort); /// the specified year, month, day, hour, minute and second.
/// </returns>
/// <summary> public static DateTime GetNetworkTimeUtc(IPAddress ntpServerAddress, Int32 port = NtpDefaultPort) {
/// Queries the DNS server for the specified record type. if(ntpServerAddress == null) {
/// </summary> throw new ArgumentNullException(nameof(ntpServerAddress));
/// <param name="query">The query.</param> }
/// <param name="recordType">Type of the record.</param>
/// <param name="ct">The cancellation token.</param> // NTP message size - 16 bytes of the digest (RFC 2030)
/// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns> Byte[] ntpData = new Byte[48];
public static Task<DnsQueryResult> QueryDnsAsync(
string query, // Setting the Leap Indicator, Version Number and Mode values
DnsRecordType recordType, ntpData[0] = 0x1B; // LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
CancellationToken ct = default)
{ // The UDP port number assigned to NTP is 123
return Task.Run(() => QueryDns(query, recordType), ct); IPEndPoint endPoint = new IPEndPoint(ntpServerAddress, port);
} Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
/// <summary> socket.Connect(endPoint);
/// Gets the UTC time by querying from an NTP server. socket.ReceiveTimeout = 3000; // Stops code hang if NTP is blocked
/// </summary> _ = socket.Send(ntpData);
/// <param name="ntpServerAddress">The NTP server address.</param> _ = socket.Receive(ntpData);
/// <param name="port">The port.</param> socket.Dispose();
/// <returns>
/// A new instance of the DateTime structure to // Offset to get to the "Transmit Timestamp" field (time at which the reply
/// the specified year, month, day, hour, minute and second. // departed the server for the client, in 64-bit timestamp format."
/// </returns> const Byte serverReplyTime = 40;
public static DateTime GetNetworkTimeUtc(IPAddress ntpServerAddress, int port = NtpDefaultPort)
{ // Get the seconds part
if (ntpServerAddress == null) UInt64 intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
throw new ArgumentNullException(nameof(ntpServerAddress));
// Get the seconds fraction
// NTP message size - 16 bytes of the digest (RFC 2030) UInt64 fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
var ntpData = new byte[48];
// Convert From big-endian to little-endian to match the platform
// Setting the Leap Indicator, Version Number and Mode values if(BitConverter.IsLittleEndian) {
ntpData[0] = 0x1B; // LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode) intPart = intPart.SwapEndianness();
fractPart = intPart.SwapEndianness();
// The UDP port number assigned to NTP is 123 }
var endPoint = new IPEndPoint(ntpServerAddress, port);
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); UInt64 milliseconds = intPart * 1000 + fractPart * 1000 / 0x100000000L;
socket.Connect(endPoint); // The time is given in UTC
socket.ReceiveTimeout = 3000; // Stops code hang if NTP is blocked return new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((Int64)milliseconds);
socket.Send(ntpData); }
socket.Receive(ntpData);
socket.Dispose(); /// <summary>
/// Gets the UTC time by querying from an NTP server.
// Offset to get to the "Transmit Timestamp" field (time at which the reply /// </summary>
// departed the server for the client, in 64-bit timestamp format." /// <param name="ntpServerName">The NTP server, by default pool.ntp.org.</param>
const byte serverReplyTime = 40; /// <param name="port">The port, by default NTP 123.</param>
/// <returns>The UTC time by querying from an NTP server.</returns>
// Get the seconds part public static DateTime GetNetworkTimeUtc(String ntpServerName = "pool.ntp.org",
ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime); Int32 port = NtpDefaultPort) {
IPAddress[] addresses = GetDnsHostEntry(ntpServerName);
// Get the seconds fraction return GetNetworkTimeUtc(addresses.First(), port);
ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4); }
// Convert From big-endian to little-endian to match the platform /// <summary>
if (BitConverter.IsLittleEndian) /// Gets the UTC time by querying from an NTP server.
{ /// </summary>
intPart = intPart.SwapEndianness(); /// <param name="ntpServerAddress">The NTP server address.</param>
fractPart = intPart.SwapEndianness(); /// <param name="port">The port.</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>
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L); public static Task<DateTime> GetNetworkTimeUtcAsync(
IPAddress ntpServerAddress,
// The time is given in UTC Int32 port = NtpDefaultPort,
return new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((long) milliseconds); CancellationToken ct = default) => 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. /// </summary>
/// </summary> /// <param name="ntpServerName">Name of the NTP server.</param>
/// <param name="ntpServerName">The NTP server, by default pool.ntp.org.</param> /// <param name="port">The port.</param>
/// <param name="port">The port, by default NTP 123.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>The UTC time by querying from an NTP server.</returns> /// <returns>The UTC time by querying from an NTP server of the result produced by this Task.</returns>
public static DateTime GetNetworkTimeUtc(string ntpServerName = "pool.ntp.org", public static Task<DateTime> GetNetworkTimeUtcAsync(
int port = NtpDefaultPort) String ntpServerName = "pool.ntp.org",
{ Int32 port = NtpDefaultPort,
var addresses = GetDnsHostEntry(ntpServerName); CancellationToken ct = default) => Task.Run(() => GetNetworkTimeUtc(ntpServerName, port), ct);
return GetNetworkTimeUtc(addresses.First(), port);
} #endregion
}
/// <summary>
/// Gets the UTC time by querying from an NTP server.
/// </summary>
/// <param name="ntpServerAddress">The NTP server address.</param>
/// <param name="port">The port.</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>
public static Task<DateTime> GetNetworkTimeUtcAsync(
IPAddress ntpServerAddress,
int port = NtpDefaultPort,
CancellationToken ct = default)
{
return Task.Run(() => GetNetworkTimeUtc(ntpServerAddress, port), ct);
}
/// <summary>
/// Gets the UTC time by querying from an NTP server.
/// </summary>
/// <param name="ntpServerName">Name of the NTP server.</param>
/// <param name="port">The port.</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>
public static Task<DateTime> GetNetworkTimeUtcAsync(
string ntpServerName = "pool.ntp.org",
int port = NtpDefaultPort,
CancellationToken ct = default)
{
return Task.Run(() => GetNetworkTimeUtc(ntpServerName, port), ct);
}
#endregion
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,258 +1,235 @@
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; namespace Unosquare.Swan.Networking {
using System.Threading.Tasks; /// <summary>
/// TCP Listener manager with built-in events and asynchronous functionality.
/// This networking component is typically used when writing server software.
/// </summary>
/// <seealso cref="System.IDisposable" />
public sealed class ConnectionListener : IDisposable {
#region Private Declarations
private readonly Object _stateLock = new Object();
private TcpListener _listenerSocket;
private Boolean _cancellationPending;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0069:Verwerfbare Felder verwerfen", Justification = "<Ausstehend>")]
private CancellationTokenSource _cancelListening;
private Task _backgroundWorkerTask;
private Boolean _hasDisposed;
#endregion
#region Events
/// <summary> /// <summary>
/// TCP Listener manager with built-in events and asynchronous functionality. /// Occurs when a new connection requests a socket from the listener.
/// This networking component is typically used when writing server software. /// Set Cancel = true to prevent the TCP client from being accepted.
/// </summary> /// </summary>
/// <seealso cref="System.IDisposable" /> public event EventHandler<ConnectionAcceptingEventArgs> OnConnectionAccepting = (s, e) => { };
public sealed class ConnectionListener : IDisposable
{ /// <summary>
#region Private Declarations /// Occurs when a new connection is accepted.
/// </summary>
private readonly object _stateLock = new object(); public event EventHandler<ConnectionAcceptedEventArgs> OnConnectionAccepted = (s, e) => { };
private TcpListener _listenerSocket;
private bool _cancellationPending; /// <summary>
private CancellationTokenSource _cancelListening; /// Occurs when a connection fails to get accepted
private Task _backgroundWorkerTask; /// </summary>
private bool _hasDisposed; public event EventHandler<ConnectionFailureEventArgs> OnConnectionFailure = (s, e) => { };
#endregion /// <summary>
/// Occurs when the listener stops.
#region Events /// </summary>
public event EventHandler<ConnectionListenerStoppedEventArgs> OnListenerStopped = (s, e) => { };
/// <summary>
/// Occurs when a new connection requests a socket from the listener. #endregion
/// Set Cancel = true to prevent the TCP client from being accepted.
/// </summary> #region Constructors
public event EventHandler<ConnectionAcceptingEventArgs> OnConnectionAccepting = (s, e) => { };
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="ConnectionListener"/> class.
/// Occurs when a new connection is accepted. /// </summary>
/// </summary> /// <param name="listenEndPoint">The listen end point.</param>
public event EventHandler<ConnectionAcceptedEventArgs> OnConnectionAccepted = (s, e) => { }; public ConnectionListener(IPEndPoint listenEndPoint) {
this.Id = Guid.NewGuid();
/// <summary> this.LocalEndPoint = listenEndPoint ?? throw new ArgumentNullException(nameof(listenEndPoint));
/// Occurs when a connection fails to get accepted }
/// </summary>
public event EventHandler<ConnectionFailureEventArgs> OnConnectionFailure = (s, e) => { }; /// <summary>
/// Initializes a new instance of the <see cref="ConnectionListener"/> class.
/// <summary> /// It uses the loopback address for listening.
/// Occurs when the listener stops. /// </summary>
/// </summary> /// <param name="listenPort">The listen port.</param>
public event EventHandler<ConnectionListenerStoppedEventArgs> OnListenerStopped = (s, e) => { }; public ConnectionListener(Int32 listenPort)
: this(new IPEndPoint(IPAddress.Loopback, listenPort)) {
#endregion }
#region Constructors /// <summary>
/// Initializes a new instance of the <see cref="ConnectionListener"/> class.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="ConnectionListener"/> class. /// <param name="listenAddress">The listen address.</param>
/// </summary> /// <param name="listenPort">The listen port.</param>
/// <param name="listenEndPoint">The listen end point.</param> public ConnectionListener(IPAddress listenAddress, Int32 listenPort)
public ConnectionListener(IPEndPoint listenEndPoint) : this(new IPEndPoint(listenAddress, listenPort)) {
{ }
Id = Guid.NewGuid();
LocalEndPoint = listenEndPoint ?? throw new ArgumentNullException(nameof(listenEndPoint)); /// <summary>
} /// Finalizes an instance of the <see cref="ConnectionListener"/> class.
/// </summary>
/// <summary> ~ConnectionListener() {
/// Initializes a new instance of the <see cref="ConnectionListener"/> class. this.Dispose(false);
/// It uses the loopback address for listening. }
/// </summary>
/// <param name="listenPort">The listen port.</param> #endregion
public ConnectionListener(int listenPort)
: this(new IPEndPoint(IPAddress.Loopback, listenPort)) #region Public Properties
{
} /// <summary>
/// Gets the local end point on which we are listening.
/// <summary> /// </summary>
/// Initializes a new instance of the <see cref="ConnectionListener"/> class. /// <value>
/// </summary> /// The local end point.
/// <param name="listenAddress">The listen address.</param> /// </value>
/// <param name="listenPort">The listen port.</param> public IPEndPoint LocalEndPoint {
public ConnectionListener(IPAddress listenAddress, int listenPort) get;
: this(new IPEndPoint(listenAddress, listenPort)) }
{
} /// <summary>
/// Gets a value indicating whether this listener is active.
/// <summary> /// </summary>
/// Finalizes an instance of the <see cref="ConnectionListener"/> class. /// <value>
/// </summary> /// <c>true</c> if this instance is listening; otherwise, <c>false</c>.
~ConnectionListener() /// </value>
{ public Boolean IsListening => this._backgroundWorkerTask != null;
Dispose(false);
} /// <summary>
/// Gets a unique identifier that gets automatically assigned upon instantiation of this class.
#endregion /// </summary>
/// <value>
#region Public Properties /// The unique identifier.
/// </value>
/// <summary> public Guid Id {
/// Gets the local end point on which we are listening. get;
/// </summary> }
/// <value>
/// The local end point. #endregion
/// </value>
public IPEndPoint LocalEndPoint { get; } #region Start and Stop
/// <summary> /// <summary>
/// Gets a value indicating whether this listener is active. /// Starts the listener in an asynchronous, non-blocking fashion.
/// </summary> /// Subscribe to the events of this class to gain access to connected client sockets.
/// <value> /// </summary>
/// <c>true</c> if this instance is listening; otherwise, <c>false</c>. /// <exception cref="System.InvalidOperationException">Cancellation has already been requested. This listener is not reusable.</exception>
/// </value> public void Start() {
public bool IsListening => _backgroundWorkerTask != null; lock(this._stateLock) {
if(this._backgroundWorkerTask != null) {
/// <summary> return;
/// Gets a unique identifier that gets automatically assigned upon instantiation of this class. }
/// </summary>
/// <value> if(this._cancellationPending) {
/// The unique identifier. throw new InvalidOperationException(
/// </value> "Cancellation has already been requested. This listener is not reusable.");
public Guid Id { get; } }
#endregion this._backgroundWorkerTask = this.DoWorkAsync();
}
#region Start and Stop }
/// <summary> /// <summary>
/// Starts the listener in an asynchronous, non-blocking fashion. /// Stops the listener from receiving new connections.
/// Subscribe to the events of this class to gain access to connected client sockets. /// This does not prevent the listener from .
/// </summary> /// </summary>
/// <exception cref="System.InvalidOperationException">Cancellation has already been requested. This listener is not reusable.</exception> public void Stop() {
public void Start() lock(this._stateLock) {
{ this._cancellationPending = true;
lock (_stateLock) this._listenerSocket?.Stop();
{ this._cancelListening?.Cancel();
if (_backgroundWorkerTask != null) this._backgroundWorkerTask?.Wait();
{ this._backgroundWorkerTask = null;
return; this._cancellationPending = false;
} }
}
if (_cancellationPending)
{ /// <summary>
throw new InvalidOperationException( /// Returns a <see cref="System.String" /> that represents this instance.
"Cancellation has already been requested. This listener is not reusable."); /// </summary>
} /// <returns>
/// A <see cref="System.String" /> that represents this instance.
_backgroundWorkerTask = DoWorkAsync(); /// </returns>
} public override String ToString() => this.LocalEndPoint.ToString();
}
/// <inheritdoc />
/// <summary> public void Dispose() {
/// Stops the listener from receiving new connections. this.Dispose(true);
/// This does not prevent the listener from . GC.SuppressFinalize(this);
/// </summary> }
public void Stop()
{ /// <summary>
lock (_stateLock) /// Releases unmanaged and - optionally - managed resources.
{ /// </summary>
_cancellationPending = true; /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
_listenerSocket?.Stop(); private void Dispose(Boolean disposing) {
_cancelListening?.Cancel(); if(this._hasDisposed) {
_backgroundWorkerTask?.Wait(); return;
_backgroundWorkerTask = null; }
_cancellationPending = false;
} if(disposing) {
} // Release managed resources
this.Stop();
/// <summary> }
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary> this._hasDisposed = true;
/// <returns> }
/// A <see cref="System.String" /> that represents this instance.
/// </returns> /// <summary>
public override string ToString() => LocalEndPoint.ToString(); /// Continuously checks for client connections until the Close method has been called.
/// </summary>
/// <inheritdoc /> /// <returns>A task that represents the asynchronous connection operation.</returns>
public void Dispose() private async Task DoWorkAsync() {
{ this._cancellationPending = false;
Dispose(true); this._listenerSocket = new TcpListener(this.LocalEndPoint);
GC.SuppressFinalize(this); this._listenerSocket.Start();
} this._cancelListening = new CancellationTokenSource();
/// <summary> try {
/// Releases unmanaged and - optionally - managed resources. while(this._cancellationPending == false) {
/// </summary> try {
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> TcpClient client = await Task.Run(() => this._listenerSocket.AcceptTcpClientAsync(), this._cancelListening.Token).ConfigureAwait(false);
private void Dispose(bool disposing) ConnectionAcceptingEventArgs acceptingArgs = new ConnectionAcceptingEventArgs(client);
{ OnConnectionAccepting(this, acceptingArgs);
if (_hasDisposed)
return; if(acceptingArgs.Cancel) {
if (disposing)
{
// Release managed resources
Stop();
}
_hasDisposed = true;
}
/// <summary>
/// Continuously checks for client connections until the Close method has been called.
/// </summary>
/// <returns>A task that represents the asynchronous connection operation.</returns>
private async Task DoWorkAsync()
{
_cancellationPending = false;
_listenerSocket = new TcpListener(LocalEndPoint);
_listenerSocket.Start();
_cancelListening = new CancellationTokenSource();
try
{
while (_cancellationPending == false)
{
try
{
var client = await Task.Run(() => _listenerSocket.AcceptTcpClientAsync(), _cancelListening.Token).ConfigureAwait(false);
var acceptingArgs = new ConnectionAcceptingEventArgs(client);
OnConnectionAccepting(this, acceptingArgs);
if (acceptingArgs.Cancel)
{
#if !NET452 #if !NET452
client.Dispose(); client.Dispose();
#else #else
client.Close(); client.Close();
#endif #endif
continue; continue;
} }
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(this.LocalEndPoint));
} catch(ObjectDisposedException) {
OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(LocalEndPoint)); OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(this.LocalEndPoint));
} } catch(Exception ex) {
catch (ObjectDisposedException) OnListenerStopped(this,
{ new ConnectionListenerStoppedEventArgs(this.LocalEndPoint, this._cancellationPending ? null : ex));
OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(LocalEndPoint)); } finally {
} this._backgroundWorkerTask = null;
catch (Exception ex) this._cancellationPending = false;
{ }
OnListenerStopped(this, }
new ConnectionListenerStoppedEventArgs(LocalEndPoint, _cancellationPending ? null : ex));
} #endregion
finally }
{
_backgroundWorkerTask = null;
_cancellationPending = false;
}
}
#endregion
}
} }

View File

@ -1,61 +1,95 @@
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 {
{ IList<DnsQuestion> Questions {
public interface IDnsMessage get;
{ }
IList<DnsQuestion> Questions { get; }
Int32 Size {
int Size { get; } get;
byte[] ToArray(); }
} Byte[] ToArray();
}
public interface IDnsMessageEntry
{ public interface IDnsMessageEntry {
DnsDomain Name { get; } DnsDomain Name {
DnsRecordType Type { get; } get;
DnsRecordClass Class { get; } }
DnsRecordType Type {
int Size { get; } get;
byte[] ToArray(); }
} DnsRecordClass Class {
get;
public interface IDnsResourceRecord : IDnsMessageEntry }
{
TimeSpan TimeToLive { get; } Int32 Size {
int DataLength { get; } get;
byte[] Data { get; } }
} Byte[] ToArray();
}
public interface IDnsRequest : IDnsMessage
{ public interface IDnsResourceRecord : IDnsMessageEntry {
int Id { get; set; } TimeSpan TimeToLive {
DnsOperationCode OperationCode { get; set; } get;
bool RecursionDesired { get; set; } }
} Int32 DataLength {
get;
public interface IDnsResponse : IDnsMessage }
{ Byte[] Data {
int Id { get; set; } get;
IList<IDnsResourceRecord> AnswerRecords { get; } }
IList<IDnsResourceRecord> AuthorityRecords { get; } }
IList<IDnsResourceRecord> AdditionalRecords { get; }
bool IsRecursionAvailable { get; set; } public interface IDnsRequest : IDnsMessage {
bool IsAuthorativeServer { get; set; } Int32 Id {
bool IsTruncated { get; set; } get; set;
DnsOperationCode OperationCode { get; set; } }
DnsResponseCode ResponseCode { get; set; } DnsOperationCode OperationCode {
} get; set;
}
public interface IDnsRequestResolver Boolean RecursionDesired {
{ get; set;
DnsClientResponse Request(DnsClientRequest request); }
} }
}
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);
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,460 +1,432 @@
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 {
{ private readonly IDnsResourceRecord _record;
public abstract class DnsResourceRecordBase : IDnsResourceRecord
{ protected DnsResourceRecordBase(IDnsResourceRecord record) => this._record = record;
private readonly IDnsResourceRecord _record;
public DnsDomain Name => this._record.Name;
protected DnsResourceRecordBase(IDnsResourceRecord record)
{ public DnsRecordType Type => this._record.Type;
_record = record;
} public DnsRecordClass Class => this._record.Class;
public DnsDomain Name => _record.Name; public TimeSpan TimeToLive => this._record.TimeToLive;
public DnsRecordType Type => _record.Type; public Int32 DataLength => this._record.DataLength;
public DnsRecordClass Class => _record.Class; public Byte[] Data => this._record.Data;
public TimeSpan TimeToLive => _record.TimeToLive; public Int32 Size => this._record.Size;
public int DataLength => _record.DataLength; protected virtual String[] IncludedProperties
=> new[] { nameof(this.Name), nameof(this.Type), nameof(this.Class), nameof(this.TimeToLive), nameof(this.DataLength) };
public byte[] Data => _record.Data;
public Byte[] ToArray() => this._record.ToArray();
public int Size => _record.Size;
public override String ToString()
protected virtual string[] IncludedProperties => Json.SerializeOnly(this, true, this.IncludedProperties);
=> new[] {nameof(Name), nameof(Type), nameof(Class), nameof(TimeToLive), nameof(DataLength)}; }
public byte[] ToArray() => _record.ToArray(); public class DnsResourceRecord : IDnsResourceRecord {
public DnsResourceRecord(
public override string ToString() DnsDomain domain,
=> Json.SerializeOnly(this, true, IncludedProperties); Byte[] data,
} DnsRecordType type,
DnsRecordClass klass = DnsRecordClass.IN,
public class DnsResourceRecord : IDnsResourceRecord TimeSpan ttl = default) {
{ this.Name = domain;
public DnsResourceRecord( this.Type = type;
DnsDomain domain, this.Class = klass;
byte[] data, this.TimeToLive = ttl;
DnsRecordType type, this.Data = data;
DnsRecordClass klass = DnsRecordClass.IN, }
TimeSpan ttl = default)
{ public DnsDomain Name {
Name = domain; get;
Type = type; }
Class = klass;
TimeToLive = ttl; public DnsRecordType Type {
Data = data; get;
} }
public DnsDomain Name { get; } public DnsRecordClass Class {
get;
public DnsRecordType Type { 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 static IList<DnsResourceRecord> GetAllFromArray( public Int32 Size => this.Name.Size + Tail.SIZE + this.Data.Length;
byte[] message,
int offset, public static IList<DnsResourceRecord> GetAllFromArray(
int count, Byte[] message,
out int endOffset) Int32 offset,
{ Int32 count,
IList<DnsResourceRecord> records = new List<DnsResourceRecord>(count); out Int32 endOffset) {
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));
} }
endOffset = offset; endOffset = offset;
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);
Byte[] data = new Byte[tail.DataLength];
var 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);
endOffset = offset + data.Length;
endOffset = offset + data.Length;
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() => new MemoryStream(this.Size)
public byte[] ToArray() .Append(this.Name.ToArray())
{ .Append(new Tail() {
return new MemoryStream(Size) Type = Type,
.Append(Name.ToArray()) Class = Class,
.Append(new Tail() TimeToLive = TimeToLive,
{ DataLength = this.Data.Length,
Type = Type, }.ToBytes())
Class = Class, .Append(this.Data)
TimeToLive = TimeToLive, .ToArray();
DataLength = Data.Length,
}.ToBytes()) public override String ToString() => Json.SerializeOnly(
.Append(Data) this,
.ToArray(); true,
} nameof(this.Name),
nameof(this.Type),
public override string ToString() nameof(this.Class),
{ nameof(this.TimeToLive),
return Json.SerializeOnly( nameof(this.DataLength));
this,
true, [StructEndianness(Endianness.Big)]
nameof(Name), [StructLayout(LayoutKind.Sequential, Pack = 2)]
nameof(Type), private struct Tail {
nameof(Class), public const Int32 SIZE = 10;
nameof(TimeToLive),
nameof(DataLength)); private UInt16 type;
} private UInt16 klass;
private UInt32 ttl;
[StructEndianness(Endianness.Big)] private UInt16 dataLength;
[StructLayout(LayoutKind.Sequential, Pack = 2)]
private struct Tail public DnsRecordType Type {
{ get => (DnsRecordType)this.type;
public const int SIZE = 10; set => this.type = (UInt16)value;
}
private ushort type;
private ushort klass; public DnsRecordClass Class {
private uint ttl; get => (DnsRecordClass)this.klass;
private ushort dataLength; set => this.klass = (UInt16)value;
}
public DnsRecordType Type
{ public TimeSpan TimeToLive {
get => (DnsRecordType) type; get => TimeSpan.FromSeconds(this.ttl);
set => type = (ushort) value; set => this.ttl = (UInt32)value.TotalSeconds;
} }
public DnsRecordClass Class public Int32 DataLength {
{ get => this.dataLength;
get => (DnsRecordClass) klass; set => this.dataLength = (UInt16)value;
set => klass = (ushort) value; }
} }
}
public TimeSpan TimeToLive
{ public class DnsPointerResourceRecord : DnsResourceRecordBase {
get => TimeSpan.FromSeconds(ttl); public DnsPointerResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
set => ttl = (uint) value.TotalSeconds; : base(record) => this.PointerDomainName = DnsDomain.FromArray(message, dataOffset);
}
public DnsDomain PointerDomainName {
public int DataLength get;
{ }
get => dataLength;
set => dataLength = (ushort) value; protected override String[] IncludedProperties {
} get {
} List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.PointerDomainName) };
} return temp.ToArray();
}
public class DnsPointerResourceRecord : DnsResourceRecordBase }
{ }
public DnsPointerResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset)
: base(record) public class DnsIPAddressResourceRecord : DnsResourceRecordBase {
{ public DnsIPAddressResourceRecord(IDnsResourceRecord record)
PointerDomainName = DnsDomain.FromArray(message, dataOffset); : base(record) => this.IPAddress = new IPAddress(this.Data);
}
public IPAddress IPAddress {
public DnsDomain PointerDomainName { get; } get;
}
protected override string[] IncludedProperties
{ protected override String[] IncludedProperties {
get get {
{ List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.IPAddress) };
var temp = new List<string>(base.IncludedProperties) {nameof(PointerDomainName)}; return temp.ToArray();
return temp.ToArray(); }
} }
} }
}
public class DnsNameServerResourceRecord : DnsResourceRecordBase {
public class DnsIPAddressResourceRecord : DnsResourceRecordBase public DnsNameServerResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
{ : base(record) => this.NSDomainName = DnsDomain.FromArray(message, dataOffset);
public DnsIPAddressResourceRecord(IDnsResourceRecord record)
: base(record) public DnsDomain NSDomainName {
{ get;
IPAddress = new IPAddress(Data); }
}
protected override String[] IncludedProperties {
public IPAddress IPAddress { get; } get {
List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.NSDomainName) };
protected override string[] IncludedProperties return temp.ToArray();
{ }
get }
{ }
var temp = new List<string>(base.IncludedProperties) {nameof(IPAddress)};
return temp.ToArray(); public class DnsCanonicalNameResourceRecord : DnsResourceRecordBase {
} public DnsCanonicalNameResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
} : base(record) => this.CanonicalDomainName = DnsDomain.FromArray(message, dataOffset);
}
public DnsDomain CanonicalDomainName {
public class DnsNameServerResourceRecord : DnsResourceRecordBase get;
{ }
public DnsNameServerResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset)
: base(record) protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
{ {
NSDomainName = DnsDomain.FromArray(message, dataOffset); nameof(this.CanonicalDomainName),
} }.ToArray();
}
public DnsDomain NSDomainName { get; }
public class DnsMailExchangeResourceRecord : DnsResourceRecordBase {
protected override string[] IncludedProperties private const Int32 PreferenceSize = 2;
{
get public DnsMailExchangeResourceRecord(
{ IDnsResourceRecord record,
var temp = new List<string>(base.IncludedProperties) {nameof(NSDomainName)}; Byte[] message,
return temp.ToArray(); Int32 dataOffset)
} : base(record) {
} Byte[] preference = new Byte[PreferenceSize];
} Array.Copy(message, dataOffset, preference, 0, preference.Length);
public class DnsCanonicalNameResourceRecord : DnsResourceRecordBase if(BitConverter.IsLittleEndian) {
{ Array.Reverse(preference);
public DnsCanonicalNameResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset) }
: base(record)
{ dataOffset += PreferenceSize;
CanonicalDomainName = DnsDomain.FromArray(message, dataOffset);
} this.Preference = BitConverter.ToUInt16(preference, 0);
this.ExchangeDomainName = DnsDomain.FromArray(message, dataOffset);
public DnsDomain CanonicalDomainName { get; } }
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties) public Int32 Preference {
{ get;
nameof(CanonicalDomainName), }
}.ToArray();
} public DnsDomain ExchangeDomainName {
get;
public class DnsMailExchangeResourceRecord : DnsResourceRecordBase }
{
private const int PreferenceSize = 2; protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
{
public DnsMailExchangeResourceRecord( nameof(this.Preference),
IDnsResourceRecord record, nameof(this.ExchangeDomainName),
byte[] message, }.ToArray();
int dataOffset) }
: base(record)
{ public class DnsStartOfAuthorityResourceRecord : DnsResourceRecordBase {
var preference = new byte[PreferenceSize]; public DnsStartOfAuthorityResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
Array.Copy(message, dataOffset, preference, 0, preference.Length); : base(record) {
this.MasterDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
if (BitConverter.IsLittleEndian) this.ResponsibleDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
{
Array.Reverse(preference); Options tail = message.ToStruct<Options>(dataOffset, Options.SIZE);
}
this.SerialNumber = tail.SerialNumber;
dataOffset += PreferenceSize; this.RefreshInterval = tail.RefreshInterval;
this.RetryInterval = tail.RetryInterval;
Preference = BitConverter.ToUInt16(preference, 0); this.ExpireInterval = tail.ExpireInterval;
ExchangeDomainName = DnsDomain.FromArray(message, dataOffset); this.MinimumTimeToLive = tail.MinimumTimeToLive;
} }
public int Preference { get; } public DnsStartOfAuthorityResourceRecord(
DnsDomain domain,
public DnsDomain ExchangeDomainName { get; } DnsDomain master,
DnsDomain responsible,
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties) Int64 serial,
{ TimeSpan refresh,
nameof(Preference), TimeSpan retry,
nameof(ExchangeDomainName), TimeSpan expire,
}.ToArray(); TimeSpan minTtl,
} TimeSpan ttl = default)
: base(Create(domain, master, responsible, serial, refresh, retry, expire, minTtl, ttl)) {
public class DnsStartOfAuthorityResourceRecord : DnsResourceRecordBase this.MasterDomainName = master;
{ this.ResponsibleDomainName = responsible;
public DnsStartOfAuthorityResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset)
: base(record) this.SerialNumber = serial;
{ this.RefreshInterval = refresh;
MasterDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset); this.RetryInterval = retry;
ResponsibleDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset); this.ExpireInterval = expire;
this.MinimumTimeToLive = minTtl;
var tail = message.ToStruct<Options>(dataOffset, Options.SIZE); }
SerialNumber = tail.SerialNumber; public DnsDomain MasterDomainName {
RefreshInterval = tail.RefreshInterval; get;
RetryInterval = tail.RetryInterval; }
ExpireInterval = tail.ExpireInterval;
MinimumTimeToLive = tail.MinimumTimeToLive; public DnsDomain ResponsibleDomainName {
} get;
}
public DnsStartOfAuthorityResourceRecord(
DnsDomain domain, public Int64 SerialNumber {
DnsDomain master, get;
DnsDomain responsible, }
long serial,
TimeSpan refresh, public TimeSpan RefreshInterval {
TimeSpan retry, get;
TimeSpan expire, }
TimeSpan minTtl,
TimeSpan ttl = default) public TimeSpan RetryInterval {
: base(Create(domain, master, responsible, serial, refresh, retry, expire, minTtl, ttl)) get;
{ }
MasterDomainName = master;
ResponsibleDomainName = responsible; public TimeSpan ExpireInterval {
get;
SerialNumber = serial; }
RefreshInterval = refresh;
RetryInterval = retry; public TimeSpan MinimumTimeToLive {
ExpireInterval = expire; get;
MinimumTimeToLive = minTtl; }
}
protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
public DnsDomain MasterDomainName { get; } {
nameof(this.MasterDomainName),
public DnsDomain ResponsibleDomainName { get; } nameof(this.ResponsibleDomainName),
nameof(this.SerialNumber),
public long SerialNumber { get; } }.ToArray();
public TimeSpan RefreshInterval { get; } private static IDnsResourceRecord Create(
DnsDomain domain,
public TimeSpan RetryInterval { get; } DnsDomain master,
DnsDomain responsible,
public TimeSpan ExpireInterval { get; } Int64 serial,
TimeSpan refresh,
public TimeSpan MinimumTimeToLive { get; } TimeSpan retry,
TimeSpan expire,
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties) TimeSpan minTtl,
{ TimeSpan ttl) {
nameof(MasterDomainName), MemoryStream data = new MemoryStream(Options.SIZE + master.Size + responsible.Size);
nameof(ResponsibleDomainName), Options tail = new Options {
nameof(SerialNumber), SerialNumber = serial,
}.ToArray(); RefreshInterval = refresh,
RetryInterval = retry,
private static IDnsResourceRecord Create( ExpireInterval = expire,
DnsDomain domain, MinimumTimeToLive = minTtl,
DnsDomain master, };
DnsDomain responsible,
long serial, _ = data.Append(master.ToArray()).Append(responsible.ToArray()).Append(tail.ToBytes());
TimeSpan refresh,
TimeSpan retry, return new DnsResourceRecord(domain, data.ToArray(), DnsRecordType.SOA, DnsRecordClass.IN, ttl);
TimeSpan expire, }
TimeSpan minTtl,
TimeSpan ttl) [StructEndianness(Endianness.Big)]
{ [StructLayout(LayoutKind.Sequential, Pack = 4)]
var data = new MemoryStream(Options.SIZE + master.Size + responsible.Size); public struct Options {
var tail = new Options public const Int32 SIZE = 20;
{
SerialNumber = serial, private UInt32 serialNumber;
RefreshInterval = refresh, private UInt32 refreshInterval;
RetryInterval = retry, private UInt32 retryInterval;
ExpireInterval = expire, private UInt32 expireInterval;
MinimumTimeToLive = minTtl, private UInt32 ttl;
};
public Int64 SerialNumber {
data.Append(master.ToArray()).Append(responsible.ToArray()).Append(tail.ToBytes()); get => this.serialNumber;
set => this.serialNumber = (UInt32)value;
return new DnsResourceRecord(domain, data.ToArray(), DnsRecordType.SOA, DnsRecordClass.IN, ttl); }
}
public TimeSpan RefreshInterval {
[StructEndianness(Endianness.Big)] get => TimeSpan.FromSeconds(this.refreshInterval);
[StructLayout(LayoutKind.Sequential, Pack = 4)] set => this.refreshInterval = (UInt32)value.TotalSeconds;
public struct Options }
{
public const int SIZE = 20; public TimeSpan RetryInterval {
get => TimeSpan.FromSeconds(this.retryInterval);
private uint serialNumber; set => this.retryInterval = (UInt32)value.TotalSeconds;
private uint refreshInterval; }
private uint retryInterval;
private uint expireInterval; public TimeSpan ExpireInterval {
private uint ttl; get => TimeSpan.FromSeconds(this.expireInterval);
set => this.expireInterval = (UInt32)value.TotalSeconds;
public long SerialNumber }
{
get => serialNumber; public TimeSpan MinimumTimeToLive {
set => serialNumber = (uint) value; get => TimeSpan.FromSeconds(this.ttl);
} set => this.ttl = (UInt32)value.TotalSeconds;
}
public TimeSpan RefreshInterval }
{ }
get => TimeSpan.FromSeconds(refreshInterval);
set => refreshInterval = (uint) value.TotalSeconds; private static class DnsResourceRecordFactory {
} public static IList<IDnsResourceRecord> GetAllFromArray(
Byte[] message,
public TimeSpan RetryInterval Int32 offset,
{ Int32 count,
get => TimeSpan.FromSeconds(retryInterval); out Int32 endOffset) {
set => retryInterval = (uint) value.TotalSeconds; List<IDnsResourceRecord> result = new List<IDnsResourceRecord>(count);
}
for(Int32 i = 0; i < count; i++) {
public TimeSpan ExpireInterval result.Add(GetFromArray(message, offset, out offset));
{ }
get => TimeSpan.FromSeconds(expireInterval);
set => expireInterval = (uint) value.TotalSeconds; endOffset = offset;
} return result;
}
public TimeSpan MinimumTimeToLive
{ private static IDnsResourceRecord GetFromArray(Byte[] message, Int32 offset, out Int32 endOffset) {
get => TimeSpan.FromSeconds(ttl); DnsResourceRecord record = DnsResourceRecord.FromArray(message, offset, out endOffset);
set => ttl = (uint) value.TotalSeconds; Int32 dataOffset = endOffset - record.DataLength;
}
} switch(record.Type) {
} case DnsRecordType.A:
case DnsRecordType.AAAA:
private static class DnsResourceRecordFactory return new DnsIPAddressResourceRecord(record);
{ case DnsRecordType.NS:
public static IList<IDnsResourceRecord> GetAllFromArray( return new DnsNameServerResourceRecord(record, message, dataOffset);
byte[] message, case DnsRecordType.CNAME:
int offset, return new DnsCanonicalNameResourceRecord(record, message, dataOffset);
int count, case DnsRecordType.SOA:
out int endOffset) return new DnsStartOfAuthorityResourceRecord(record, message, dataOffset);
{ case DnsRecordType.PTR:
var result = new List<IDnsResourceRecord>(count); return new DnsPointerResourceRecord(record, message, dataOffset);
case DnsRecordType.MX:
for (var i = 0; i < count; i++) return new DnsMailExchangeResourceRecord(record, message, dataOffset);
{ default:
result.Add(GetFromArray(message, offset, out offset)); return record;
} }
}
endOffset = offset; }
return result; }
}
private static IDnsResourceRecord GetFromArray(byte[] message, int offset, out int endOffset)
{
var record = DnsResourceRecord.FromArray(message, offset, out endOffset);
var dataOffset = endOffset - record.DataLength;
switch (record.Type)
{
case DnsRecordType.A:
case DnsRecordType.AAAA:
return new DnsIPAddressResourceRecord(record);
case DnsRecordType.NS:
return new DnsNameServerResourceRecord(record, message, dataOffset);
case DnsRecordType.CNAME:
return new DnsCanonicalNameResourceRecord(record, message, dataOffset);
case DnsRecordType.SOA:
return new DnsStartOfAuthorityResourceRecord(record, message, dataOffset);
case DnsRecordType.PTR:
return new DnsPointerResourceRecord(record, message, dataOffset);
case DnsRecordType.MX:
return new DnsMailExchangeResourceRecord(record, message, dataOffset);
default:
return record;
}
}
}
}
} }

View File

@ -1,215 +1,205 @@
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 {
{ private readonly DnsResponse _response;
public class DnsClientResponse : IDnsResponse private readonly Byte[] _message;
{
private readonly DnsResponse _response; internal DnsClientResponse(DnsClientRequest request, DnsResponse response, Byte[] message) {
private readonly byte[] _message; this.Request = request;
internal DnsClientResponse(DnsClientRequest request, DnsResponse response, byte[] message) this._message = message;
{ this._response = response;
Request = request; }
_message = message; public DnsClientRequest Request {
_response = response; get;
} }
public DnsClientRequest Request { get; } public Int32 Id {
get => this._response.Id;
public int Id set {
{ }
get { return _response.Id; } }
set { }
} public IList<IDnsResourceRecord> AnswerRecords => this._response.AnswerRecords;
public IList<IDnsResourceRecord> AnswerRecords => _response.AnswerRecords; public IList<IDnsResourceRecord> AuthorityRecords =>
new ReadOnlyCollection<IDnsResourceRecord>(this._response.AuthorityRecords);
public IList<IDnsResourceRecord> AuthorityRecords =>
new ReadOnlyCollection<IDnsResourceRecord>(_response.AuthorityRecords); public IList<IDnsResourceRecord> AdditionalRecords =>
new ReadOnlyCollection<IDnsResourceRecord>(this._response.AdditionalRecords);
public IList<IDnsResourceRecord> AdditionalRecords =>
new ReadOnlyCollection<IDnsResourceRecord>(_response.AdditionalRecords); public Boolean IsRecursionAvailable {
get => this._response.IsRecursionAvailable;
public bool IsRecursionAvailable set {
{ }
get { return _response.IsRecursionAvailable; } }
set { }
} public Boolean IsAuthorativeServer {
get => this._response.IsAuthorativeServer;
public bool IsAuthorativeServer set {
{ }
get { return _response.IsAuthorativeServer; } }
set { }
} public Boolean IsTruncated {
get => this._response.IsTruncated;
public bool IsTruncated set {
{ }
get { return _response.IsTruncated; } }
set { }
} public DnsOperationCode OperationCode {
get => this._response.OperationCode;
public DnsOperationCode OperationCode set {
{ }
get { return _response.OperationCode; } }
set { }
} public DnsResponseCode ResponseCode {
get => this._response.ResponseCode;
public DnsResponseCode ResponseCode set {
{ }
get { return _response.ResponseCode; } }
set { }
} public IList<DnsQuestion> Questions => new ReadOnlyCollection<DnsQuestion>(this._response.Questions);
public IList<DnsQuestion> Questions => new ReadOnlyCollection<DnsQuestion>(_response.Questions); public Int32 Size => this._message.Length;
public int Size => _message.Length; public Byte[] ToArray() => this._message;
public byte[] ToArray() => _message; public override String ToString() => this._response.ToString();
}
public override string ToString() => _response.ToString();
} public class DnsResponse : IDnsResponse {
private DnsHeader _header;
public class DnsResponse : IDnsResponse
{ public DnsResponse(
private DnsHeader _header; DnsHeader header,
IList<DnsQuestion> questions,
public DnsResponse( IList<IDnsResourceRecord> answers,
DnsHeader header, IList<IDnsResourceRecord> authority,
IList<DnsQuestion> questions, IList<IDnsResourceRecord> additional) {
IList<IDnsResourceRecord> answers, this._header = header;
IList<IDnsResourceRecord> authority, this.Questions = questions;
IList<IDnsResourceRecord> additional) this.AnswerRecords = answers;
{ this.AuthorityRecords = authority;
_header = header; this.AdditionalRecords = additional;
Questions = questions; }
AnswerRecords = answers;
AuthorityRecords = authority; public IList<DnsQuestion> Questions {
AdditionalRecords = additional; get;
} }
public IList<DnsQuestion> Questions { get; } public IList<IDnsResourceRecord> AnswerRecords {
get;
public IList<IDnsResourceRecord> AnswerRecords { get; } }
public IList<IDnsResourceRecord> AuthorityRecords { get; } public IList<IDnsResourceRecord> AuthorityRecords {
get;
public IList<IDnsResourceRecord> AdditionalRecords { get; } }
public int Id public IList<IDnsResourceRecord> AdditionalRecords {
{ get;
get => _header.Id; }
set => _header.Id = value;
} public Int32 Id {
get => this._header.Id;
public bool IsRecursionAvailable set => this._header.Id = value;
{ }
get => _header.RecursionAvailable;
set => _header.RecursionAvailable = value; public Boolean IsRecursionAvailable {
} get => this._header.RecursionAvailable;
set => this._header.RecursionAvailable = value;
public bool IsAuthorativeServer }
{
get => _header.AuthorativeServer; public Boolean IsAuthorativeServer {
set => _header.AuthorativeServer = value; get => this._header.AuthorativeServer;
} set => this._header.AuthorativeServer = value;
}
public bool IsTruncated
{ public Boolean IsTruncated {
get => _header.Truncated; get => this._header.Truncated;
set => _header.Truncated = value; set => this._header.Truncated = value;
} }
public DnsOperationCode OperationCode public DnsOperationCode OperationCode {
{ get => this._header.OperationCode;
get => _header.OperationCode; set => this._header.OperationCode = value;
set => _header.OperationCode = value; }
}
public DnsResponseCode ResponseCode {
public DnsResponseCode ResponseCode get => this._header.ResponseCode;
{ set => this._header.ResponseCode = value;
get => _header.ResponseCode; }
set => _header.ResponseCode = value;
} public Int32 Size
=> this._header.Size +
public int Size this.Questions.Sum(q => q.Size) +
=> _header.Size + this.AnswerRecords.Sum(a => a.Size) +
Questions.Sum(q => q.Size) + this.AuthorityRecords.Sum(a => a.Size) +
AnswerRecords.Sum(a => a.Size) + this.AdditionalRecords.Sum(a => a.Size);
AuthorityRecords.Sum(a => a.Size) +
AdditionalRecords.Sum(a => a.Size); public static DnsResponse FromArray(Byte[] message) {
DnsHeader header = DnsHeader.FromArray(message);
public static DnsResponse FromArray(byte[] message) Int32 offset = header.Size;
{
var header = DnsHeader.FromArray(message); if(!header.Response || header.QuestionCount == 0) {
var offset = header.Size; throw new ArgumentException("Invalid response message");
}
if (!header.Response || header.QuestionCount == 0)
{ return header.Truncated
throw new ArgumentException("Invalid response message"); ? new DnsResponse(header,
} DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount),
new List<IDnsResourceRecord>(),
if (header.Truncated) new List<IDnsResourceRecord>(),
{ new List<IDnsResourceRecord>())
return new DnsResponse(header, : new DnsResponse(header,
DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount), DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount, out offset),
new List<IDnsResourceRecord>(), DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AnswerRecordCount, out offset),
new List<IDnsResourceRecord>(), DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AuthorityRecordCount, out offset),
new List<IDnsResourceRecord>()); DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AdditionalRecordCount, out _));
} }
return new DnsResponse(header, public Byte[] ToArray() {
DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount, out offset), this.UpdateHeader();
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AnswerRecordCount, out offset), MemoryStream result = new MemoryStream(this.Size);
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AuthorityRecordCount, out offset),
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AdditionalRecordCount, out offset)); _ = result
} .Append(this._header.ToArray())
.Append(this.Questions.Select(q => q.ToArray()))
public byte[] ToArray() .Append(this.AnswerRecords.Select(a => a.ToArray()))
{ .Append(this.AuthorityRecords.Select(a => a.ToArray()))
UpdateHeader(); .Append(this.AdditionalRecords.Select(a => a.ToArray()));
var result = new MemoryStream(Size);
return result.ToArray();
result }
.Append(_header.ToArray())
.Append(Questions.Select(q => q.ToArray())) public override String ToString() {
.Append(AnswerRecords.Select(a => a.ToArray())) this.UpdateHeader();
.Append(AuthorityRecords.Select(a => a.ToArray()))
.Append(AdditionalRecords.Select(a => a.ToArray())); return Json.SerializeOnly(
this,
return result.ToArray(); true,
} nameof(this.Questions),
nameof(this.AnswerRecords),
public override string ToString() nameof(this.AuthorityRecords),
{ nameof(this.AdditionalRecords));
UpdateHeader(); }
return Json.SerializeOnly( private void UpdateHeader() {
this, this._header.QuestionCount = this.Questions.Count;
true, this._header.AnswerRecordCount = this.AnswerRecords.Count;
nameof(Questions), this._header.AuthorityRecordCount = this.AuthorityRecords.Count;
nameof(AnswerRecords), this._header.AdditionalRecordCount = this.AdditionalRecords.Count;
nameof(AuthorityRecords), }
nameof(AdditionalRecords)); }
} }
private void UpdateHeader()
{
_header.QuestionCount = Questions.Count;
_header.AnswerRecordCount = AnswerRecords.Count;
_header.AuthorityRecordCount = AuthorityRecords.Count;
_header.AdditionalRecordCount = AdditionalRecords.Count;
}
}
}
} }

View File

@ -1,88 +1,77 @@
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 IDnsRequestResolver _resolver;
private readonly IPEndPoint _dns;
private readonly IDnsRequestResolver _resolver; public DnsClient(IPEndPoint dns, IDnsRequestResolver resolver = null) {
this._dns = dns;
public DnsClient(IPEndPoint dns, IDnsRequestResolver resolver = null) this._resolver = resolver ?? new DnsUdpRequestResolver(new DnsTcpRequestResolver());
{ }
_dns = dns;
_resolver = resolver ?? new DnsUdpRequestResolver(new DnsTcpRequestResolver()); public DnsClient(IPAddress ip, Int32 port = Network.DnsDefaultPort, IDnsRequestResolver resolver = null)
} : this(new IPEndPoint(ip, port), resolver) {
}
public DnsClient(IPAddress ip, int port = Network.DnsDefaultPort, IDnsRequestResolver resolver = null)
: this(new IPEndPoint(ip, port), resolver) public DnsClientRequest Create(IDnsRequest request = null) => new DnsClientRequest(this._dns, request, this._resolver);
{
} public IList<IPAddress> Lookup(String domain, DnsRecordType type = DnsRecordType.A) {
if(String.IsNullOrWhiteSpace(domain)) {
public DnsClientRequest Create(IDnsRequest request = null) throw new ArgumentNullException(nameof(domain));
{ }
return new DnsClientRequest(_dns, request, _resolver);
} if(type != DnsRecordType.A && type != DnsRecordType.AAAA) {
throw new ArgumentException("Invalid record type " + type);
public IList<IPAddress> Lookup(string domain, DnsRecordType type = DnsRecordType.A) }
{
if (string.IsNullOrWhiteSpace(domain)) DnsClientResponse response = this.Resolve(domain, type);
throw new ArgumentNullException(nameof(domain)); List<IPAddress> ips = response.AnswerRecords
.Where(r => r.Type == type)
if (type != DnsRecordType.A && type != DnsRecordType.AAAA) .Cast<DnsIPAddressResourceRecord>()
{ .Select(r => r.IPAddress)
throw new ArgumentException("Invalid record type " + type); .ToList();
}
if(ips.Count == 0) {
var response = Resolve(domain, type); throw new DnsQueryException(response, "No matching records");
var ips = response.AnswerRecords }
.Where(r => r.Type == type)
.Cast<DnsIPAddressResourceRecord>() return ips;
.Select(r => r.IPAddress) }
.ToList();
public String Reverse(IPAddress ip) {
if (ips.Count == 0) if(ip == null) {
{ throw new ArgumentNullException(nameof(ip));
throw new DnsQueryException(response, "No matching records"); }
}
DnsClientResponse response = this.Resolve(DnsDomain.PointerName(ip), DnsRecordType.PTR);
return ips; IDnsResourceRecord ptr = response.AnswerRecords.FirstOrDefault(r => r.Type == DnsRecordType.PTR);
}
if(ptr == null) {
public string Reverse(IPAddress ip) throw new DnsQueryException(response, "No matching records");
{ }
if (ip == null)
throw new ArgumentNullException(nameof(ip)); return ((DnsPointerResourceRecord)ptr).PointerDomainName.ToString();
}
var response = Resolve(DnsDomain.PointerName(ip), DnsRecordType.PTR);
var ptr = response.AnswerRecords.FirstOrDefault(r => r.Type == DnsRecordType.PTR); public DnsClientResponse Resolve(String domain, DnsRecordType type) => this.Resolve(new DnsDomain(domain), type);
if (ptr == null) public DnsClientResponse Resolve(DnsDomain domain, DnsRecordType type) {
{ DnsClientRequest request = this.Create();
throw new DnsQueryException(response, "No matching records"); DnsQuestion question = new DnsQuestion(domain, type);
}
request.Questions.Add(question);
return ((DnsPointerResourceRecord)ptr).PointerDomainName.ToString(); request.OperationCode = DnsOperationCode.Query;
} request.RecursionDesired = true;
public DnsClientResponse Resolve(string domain, DnsRecordType type) => Resolve(new DnsDomain(domain), type); return request.Resolve();
}
public DnsClientResponse Resolve(DnsDomain domain, DnsRecordType type) }
{
var request = Create();
var question = new DnsQuestion(domain, type);
request.Questions.Add(question);
request.OperationCode = DnsOperationCode.Query;
request.RecursionDesired = true;
return request.Resolve();
}
}
} }

View File

@ -1,123 +1,132 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Collections.Generic;
using System.Collections.Generic;
namespace Unosquare.Swan.Networking {
/// <summary>
/// Represents a response from a DNS server.
/// </summary>
public class DnsQueryResult {
private readonly List<DnsRecord> m_AnswerRecords = new List<DnsRecord>();
private readonly List<DnsRecord> m_AdditionalRecords = new List<DnsRecord>();
private readonly List<DnsRecord> m_AuthorityRecords = new List<DnsRecord>();
/// <summary> /// <summary>
/// Represents a response from a DNS server. /// Initializes a new instance of the <see cref="DnsQueryResult"/> class.
/// </summary> /// </summary>
public class DnsQueryResult /// <param name="response">The response.</param>
{ internal DnsQueryResult(DnsClient.DnsClientResponse response)
private readonly List<DnsRecord> m_AnswerRecords = new List<DnsRecord>(); : this() {
private readonly List<DnsRecord> m_AdditionalRecords = new List<DnsRecord>(); this.Id = response.Id;
private readonly List<DnsRecord> m_AuthorityRecords = new List<DnsRecord>(); this.IsAuthoritativeServer = response.IsAuthorativeServer;
this.IsRecursionAvailable = response.IsRecursionAvailable;
/// <summary> this.IsTruncated = response.IsTruncated;
/// Initializes a new instance of the <see cref="DnsQueryResult"/> class. this.OperationCode = response.OperationCode;
/// </summary> this.ResponseCode = response.ResponseCode;
/// <param name="response">The response.</param>
internal DnsQueryResult(DnsClient.DnsClientResponse response) if(response.AnswerRecords != null) {
: this() foreach(DnsClient.IDnsResourceRecord record in response.AnswerRecords) {
{ this.AnswerRecords.Add(new DnsRecord(record));
Id = response.Id; }
IsAuthoritativeServer = response.IsAuthorativeServer; }
IsRecursionAvailable = response.IsRecursionAvailable;
IsTruncated = response.IsTruncated; if(response.AuthorityRecords != null) {
OperationCode = response.OperationCode; foreach(DnsClient.IDnsResourceRecord record in response.AuthorityRecords) {
ResponseCode = response.ResponseCode; this.AuthorityRecords.Add(new DnsRecord(record));
}
if (response.AnswerRecords != null) }
{
foreach (var record in response.AnswerRecords) if(response.AdditionalRecords != null) {
AnswerRecords.Add(new DnsRecord(record)); foreach(DnsClient.IDnsResourceRecord record in response.AdditionalRecords) {
} this.AdditionalRecords.Add(new DnsRecord(record));
}
if (response.AuthorityRecords != null) }
{ }
foreach (var record in response.AuthorityRecords)
AuthorityRecords.Add(new DnsRecord(record)); private DnsQueryResult() {
} }
if (response.AdditionalRecords != null) /// <summary>
{ /// Gets the identifier.
foreach (var record in response.AdditionalRecords) /// </summary>
AdditionalRecords.Add(new DnsRecord(record)); /// <value>
} /// The identifier.
} /// </value>
public Int32 Id {
private DnsQueryResult() get;
{ }
}
/// <summary>
/// <summary> /// Gets a value indicating whether this instance is authoritative server.
/// Gets the identifier. /// </summary>
/// </summary> /// <value>
/// <value> /// <c>true</c> if this instance is authoritative server; otherwise, <c>false</c>.
/// The identifier. /// </value>
/// </value> public Boolean IsAuthoritativeServer {
public int Id { get; } get;
}
/// <summary>
/// Gets a value indicating whether this instance is authoritative server. /// <summary>
/// </summary> /// Gets a value indicating whether this instance is truncated.
/// <value> /// </summary>
/// <c>true</c> if this instance is authoritative server; otherwise, <c>false</c>. /// <value>
/// </value> /// <c>true</c> if this instance is truncated; otherwise, <c>false</c>.
public bool IsAuthoritativeServer { get; } /// </value>
public Boolean IsTruncated {
/// <summary> get;
/// Gets a value indicating whether this instance is truncated. }
/// </summary>
/// <value> /// <summary>
/// <c>true</c> if this instance is truncated; otherwise, <c>false</c>. /// Gets a value indicating whether this instance is recursion available.
/// </value> /// </summary>
public bool IsTruncated { get; } /// <value>
/// <c>true</c> if this instance is recursion available; otherwise, <c>false</c>.
/// <summary> /// </value>
/// Gets a value indicating whether this instance is recursion available. public Boolean IsRecursionAvailable {
/// </summary> get;
/// <value> }
/// <c>true</c> if this instance is recursion available; otherwise, <c>false</c>.
/// </value> /// <summary>
public bool IsRecursionAvailable { get; } /// Gets the operation code.
/// </summary>
/// <summary> /// <value>
/// Gets the operation code. /// The operation code.
/// </summary> /// </value>
/// <value> public DnsOperationCode OperationCode {
/// The operation code. get;
/// </value> }
public DnsOperationCode OperationCode { get; }
/// <summary>
/// <summary> /// Gets the response code.
/// Gets the response code. /// </summary>
/// </summary> /// <value>
/// <value> /// The response code.
/// The response code. /// </value>
/// </value> public DnsResponseCode ResponseCode {
public DnsResponseCode ResponseCode { get; } get;
}
/// <summary>
/// Gets the answer records. /// <summary>
/// </summary> /// Gets the answer records.
/// <value> /// </summary>
/// The answer records. /// <value>
/// </value> /// The answer records.
public IList<DnsRecord> AnswerRecords => m_AnswerRecords; /// </value>
public IList<DnsRecord> AnswerRecords => this.m_AnswerRecords;
/// <summary>
/// Gets the additional records. /// <summary>
/// </summary> /// Gets the additional records.
/// <value> /// </summary>
/// The additional records. /// <value>
/// </value> /// The additional records.
public IList<DnsRecord> AdditionalRecords => m_AdditionalRecords; /// </value>
public IList<DnsRecord> AdditionalRecords => this.m_AdditionalRecords;
/// <summary>
/// Gets the authority records. /// <summary>
/// </summary> /// Gets the authority records.
/// <value> /// </summary>
/// The authority records. /// <value>
/// </value> /// The authority records.
public IList<DnsRecord> AuthorityRecords => m_AuthorityRecords; /// </value>
} public IList<DnsRecord> AuthorityRecords => this.m_AuthorityRecords;
}
} }

View File

@ -1,208 +1,240 @@
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>
/// Represents a DNS record entry.
/// </summary>
public class DnsRecord {
/// <summary> /// <summary>
/// Represents a DNS record entry. /// Initializes a new instance of the <see cref="DnsRecord"/> class.
/// </summary> /// </summary>
public class DnsRecord /// <param name="record">The record.</param>
{ internal DnsRecord(DnsClient.IDnsResourceRecord record)
/// <summary> : this() {
/// Initializes a new instance of the <see cref="DnsRecord"/> class. this.Name = record.Name.ToString();
/// </summary> this.Type = record.Type;
/// <param name="record">The record.</param> this.Class = record.Class;
internal DnsRecord(DnsClient.IDnsResourceRecord record) this.TimeToLive = record.TimeToLive;
: this() this.Data = record.Data;
{
Name = record.Name.ToString(); // PTR
Type = record.Type; this.PointerDomainName = (record as DnsClient.DnsPointerResourceRecord)?.PointerDomainName?.ToString();
Class = record.Class;
TimeToLive = record.TimeToLive; // A
Data = record.Data; this.IPAddress = (record as DnsClient.DnsIPAddressResourceRecord)?.IPAddress;
// PTR // NS
PointerDomainName = (record as DnsClient.DnsPointerResourceRecord)?.PointerDomainName?.ToString(); this.NameServerDomainName = (record as DnsClient.DnsNameServerResourceRecord)?.NSDomainName?.ToString();
// A // CNAME
IPAddress = (record as DnsClient.DnsIPAddressResourceRecord)?.IPAddress; this.CanonicalDomainName = (record as DnsClient.DnsCanonicalNameResourceRecord)?.CanonicalDomainName.ToString();
// NS // MX
NameServerDomainName = (record as DnsClient.DnsNameServerResourceRecord)?.NSDomainName?.ToString(); this.MailExchangerDomainName = (record as DnsClient.DnsMailExchangeResourceRecord)?.ExchangeDomainName.ToString();
this.MailExchangerPreference = (record as DnsClient.DnsMailExchangeResourceRecord)?.Preference;
// CNAME
CanonicalDomainName = (record as DnsClient.DnsCanonicalNameResourceRecord)?.CanonicalDomainName.ToString(); // SOA
this.SoaMasterDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MasterDomainName.ToString();
// MX this.SoaResponsibleDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ResponsibleDomainName.ToString();
MailExchangerDomainName = (record as DnsClient.DnsMailExchangeResourceRecord)?.ExchangeDomainName.ToString(); this.SoaSerialNumber = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.SerialNumber;
MailExchangerPreference = (record as DnsClient.DnsMailExchangeResourceRecord)?.Preference; this.SoaRefreshInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RefreshInterval;
this.SoaRetryInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RetryInterval;
// SOA this.SoaExpireInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ExpireInterval;
SoaMasterDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MasterDomainName.ToString(); this.SoaMinimumTimeToLive = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MinimumTimeToLive;
SoaResponsibleDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ResponsibleDomainName.ToString(); }
SoaSerialNumber = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.SerialNumber;
SoaRefreshInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RefreshInterval; private DnsRecord() {
SoaRetryInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RetryInterval; // placeholder
SoaExpireInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ExpireInterval; }
SoaMinimumTimeToLive = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MinimumTimeToLive;
} /// <summary>
/// Gets the name.
private DnsRecord() /// </summary>
{ /// <value>
// placeholder /// The name.
} /// </value>
public String Name {
/// <summary> get;
/// Gets the name. }
/// </summary>
/// <value> /// <summary>
/// The name. /// Gets the type.
/// </value> /// </summary>
public string Name { get; } /// <value>
/// The type.
/// <summary> /// </value>
/// Gets the type. public DnsRecordType Type {
/// </summary> get;
/// <value> }
/// The type.
/// </value> /// <summary>
public DnsRecordType Type { get; } /// Gets the class.
/// </summary>
/// <summary> /// <value>
/// Gets the class. /// The class.
/// </summary> /// </value>
/// <value> public DnsRecordClass Class {
/// The class. get;
/// </value> }
public DnsRecordClass Class { get; }
/// <summary>
/// <summary> /// Gets the time to live.
/// Gets the time to live. /// </summary>
/// </summary> /// <value>
/// <value> /// The time to live.
/// The time to live. /// </value>
/// </value> public TimeSpan TimeToLive {
public TimeSpan TimeToLive { get; } get;
}
/// <summary>
/// Gets the raw data of the record. /// <summary>
/// </summary> /// Gets the raw data of the record.
/// <value> /// </summary>
/// The data. /// <value>
/// </value> /// The data.
public byte[] Data { get; } /// </value>
public Byte[] Data {
/// <summary> get;
/// Gets the data text bytes in ASCII encoding. }
/// </summary>
/// <value> /// <summary>
/// The data text. /// Gets the data text bytes in ASCII encoding.
/// </value> /// </summary>
public string DataText => Data == null ? string.Empty : Encoding.ASCII.GetString(Data); /// <value>
/// The data text.
/// <summary> /// </value>
/// Gets the name of the pointer domain. public String DataText => this.Data == null ? String.Empty : Encoding.ASCII.GetString(this.Data);
/// </summary>
/// <value> /// <summary>
/// The name of the pointer domain. /// Gets the name of the pointer domain.
/// </value> /// </summary>
public string PointerDomainName { get; } /// <value>
/// The name of the pointer domain.
/// <summary> /// </value>
/// Gets the ip address. public String PointerDomainName {
/// </summary> get;
/// <value> }
/// The ip address.
/// </value> /// <summary>
public IPAddress IPAddress { get; } /// Gets the ip address.
/// </summary>
/// <summary> /// <value>
/// Gets the name of the name server domain. /// The ip address.
/// </summary> /// </value>
/// <value> public IPAddress IPAddress {
/// The name of the name server domain. get;
/// </value> }
public string NameServerDomainName { get; }
/// <summary>
/// <summary> /// Gets the name of the name server domain.
/// Gets the name of the canonical domain. /// </summary>
/// </summary> /// <value>
/// <value> /// The name of the name server domain.
/// The name of the canonical domain. /// </value>
/// </value> public String NameServerDomainName {
public string CanonicalDomainName { get; } get;
}
/// <summary>
/// Gets the mail exchanger preference. /// <summary>
/// </summary> /// Gets the name of the canonical domain.
/// <value> /// </summary>
/// The mail exchanger preference. /// <value>
/// </value> /// The name of the canonical domain.
public int? MailExchangerPreference { get; } /// </value>
public String CanonicalDomainName {
/// <summary> get;
/// Gets the name of the mail exchanger domain. }
/// </summary>
/// <value> /// <summary>
/// The name of the mail exchanger domain. /// Gets the mail exchanger preference.
/// </value> /// </summary>
public string MailExchangerDomainName { get; } /// <value>
/// The mail exchanger preference.
/// <summary> /// </value>
/// Gets the name of the soa master domain. public Int32? MailExchangerPreference {
/// </summary> get;
/// <value> }
/// The name of the soa master domain.
/// </value> /// <summary>
public string SoaMasterDomainName { get; } /// Gets the name of the mail exchanger domain.
/// </summary>
/// <summary> /// <value>
/// Gets the name of the soa responsible domain. /// The name of the mail exchanger domain.
/// </summary> /// </value>
/// <value> public String MailExchangerDomainName {
/// The name of the soa responsible domain. get;
/// </value> }
public string SoaResponsibleDomainName { get; }
/// <summary>
/// <summary> /// Gets the name of the soa master domain.
/// Gets the soa serial number. /// </summary>
/// </summary> /// <value>
/// <value> /// The name of the soa master domain.
/// The soa serial number. /// </value>
/// </value> public String SoaMasterDomainName {
public long? SoaSerialNumber { get; } get;
}
/// <summary>
/// Gets the soa refresh interval. /// <summary>
/// </summary> /// Gets the name of the soa responsible domain.
/// <value> /// </summary>
/// The soa refresh interval. /// <value>
/// </value> /// The name of the soa responsible domain.
public TimeSpan? SoaRefreshInterval { get; } /// </value>
public String SoaResponsibleDomainName {
/// <summary> get;
/// Gets the soa retry interval. }
/// </summary>
/// <value> /// <summary>
/// The soa retry interval. /// Gets the soa serial number.
/// </value> /// </summary>
public TimeSpan? SoaRetryInterval { get; } /// <value>
/// The soa serial number.
/// <summary> /// </value>
/// Gets the soa expire interval. public Int64? SoaSerialNumber {
/// </summary> get;
/// <value> }
/// The soa expire interval.
/// </value> /// <summary>
public TimeSpan? SoaExpireInterval { get; } /// Gets the soa refresh interval.
/// </summary>
/// <summary> /// <value>
/// Gets the soa minimum time to live. /// The soa refresh interval.
/// </summary> /// </value>
/// <value> public TimeSpan? SoaRefreshInterval {
/// The soa minimum time to live. get;
/// </value> }
public TimeSpan? SoaMinimumTimeToLive { get; }
} /// <summary>
/// Gets the soa retry interval.
/// </summary>
/// <value>
/// The soa retry interval.
/// </value>
public TimeSpan? SoaRetryInterval {
get;
}
/// <summary>
/// Gets the soa expire interval.
/// </summary>
/// <value>
/// The soa expire interval.
/// </value>
public TimeSpan? SoaExpireInterval {
get;
}
/// <summary>
/// Gets the soa minimum time to live.
/// </summary>
/// <value>
/// The soa minimum time to live.
/// </value>
public TimeSpan? SoaMinimumTimeToLive {
get;
}
}
} }

View File

@ -1,177 +1,172 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace Unosquare.Swan.Networking namespace Unosquare.Swan.Networking {
{ #region DNS
#region DNS
/// <summary>
/// Enumerates the different DNS record types.
/// </summary>
public enum DnsRecordType {
/// <summary> /// <summary>
/// Enumerates the different DNS record types. /// A records
/// </summary> /// </summary>
public enum DnsRecordType A = 1,
{
/// <summary>
/// A records
/// </summary>
A = 1,
/// <summary>
/// Nameserver records
/// </summary>
NS = 2,
/// <summary>
/// CNAME records
/// </summary>
CNAME = 5,
/// <summary>
/// SOA records
/// </summary>
SOA = 6,
/// <summary>
/// WKS records
/// </summary>
WKS = 11,
/// <summary>
/// PTR records
/// </summary>
PTR = 12,
/// <summary>
/// MX records
/// </summary>
MX = 15,
/// <summary>
/// TXT records
/// </summary>
TXT = 16,
/// <summary>
/// A records fot IPv6
/// </summary>
AAAA = 28,
/// <summary>
/// SRV records
/// </summary>
SRV = 33,
/// <summary>
/// ANY records
/// </summary>
ANY = 255,
}
/// <summary> /// <summary>
/// Enumerates the different DNS record classes. /// Nameserver records
/// </summary> /// </summary>
public enum DnsRecordClass NS = 2,
{
/// <summary>
/// IN records
/// </summary>
IN = 1,
/// <summary>
/// ANY records
/// </summary>
ANY = 255,
}
/// <summary> /// <summary>
/// Enumerates the different DNS operation codes. /// CNAME records
/// </summary> /// </summary>
public enum DnsOperationCode CNAME = 5,
{
/// <summary>
/// Query operation
/// </summary>
Query = 0,
/// <summary>
/// IQuery operation
/// </summary>
IQuery,
/// <summary>
/// Status operation
/// </summary>
Status,
/// <summary>
/// Notify operation
/// </summary>
Notify = 4,
/// <summary>
/// Update operation
/// </summary>
Update,
}
/// <summary> /// <summary>
/// Enumerates the different DNS query response codes. /// SOA records
/// </summary> /// </summary>
public enum DnsResponseCode SOA = 6,
{
/// <summary> /// <summary>
/// No error /// WKS records
/// </summary> /// </summary>
NoError = 0, WKS = 11,
/// <summary> /// <summary>
/// No error /// PTR records
/// </summary> /// </summary>
FormatError, PTR = 12,
/// <summary> /// <summary>
/// Format error /// MX records
/// </summary> /// </summary>
ServerFailure, MX = 15,
/// <summary> /// <summary>
/// Server failure error /// TXT records
/// </summary> /// </summary>
NameError, TXT = 16,
/// <summary> /// <summary>
/// Name error /// A records fot IPv6
/// </summary> /// </summary>
NotImplemented, AAAA = 28,
/// <summary> /// <summary>
/// Not implemented error /// SRV records
/// </summary> /// </summary>
Refused, SRV = 33,
/// <summary> /// <summary>
/// Refused error /// ANY records
/// </summary> /// </summary>
YXDomain, ANY = 255,
}
/// <summary>
/// YXRR error /// <summary>
/// </summary> /// Enumerates the different DNS record classes.
YXRRSet, /// </summary>
public enum DnsRecordClass {
/// <summary> /// <summary>
/// NXRR Set error /// IN records
/// </summary> /// </summary>
NXRRSet, IN = 1,
/// <summary> /// <summary>
/// Not authorized error /// ANY records
/// </summary> /// </summary>
NotAuth, ANY = 255,
}
/// <summary>
/// Not zone error /// <summary>
/// </summary> /// Enumerates the different DNS operation codes.
NotZone, /// </summary>
} public enum DnsOperationCode {
/// <summary>
#endregion /// Query operation
/// </summary>
Query = 0,
/// <summary>
/// IQuery operation
/// </summary>
IQuery,
/// <summary>
/// Status operation
/// </summary>
Status,
/// <summary>
/// Notify operation
/// </summary>
Notify = 4,
/// <summary>
/// Update operation
/// </summary>
Update,
}
/// <summary>
/// Enumerates the different DNS query response codes.
/// </summary>
public enum DnsResponseCode {
/// <summary>
/// No error
/// </summary>
NoError = 0,
/// <summary>
/// No error
/// </summary>
FormatError,
/// <summary>
/// Format error
/// </summary>
ServerFailure,
/// <summary>
/// Server failure error
/// </summary>
NameError,
/// <summary>
/// Name error
/// </summary>
NotImplemented,
/// <summary>
/// Not implemented error
/// </summary>
Refused,
/// <summary>
/// Refused error
/// </summary>
YXDomain,
/// <summary>
/// YXRR error
/// </summary>
YXRRSet,
/// <summary>
/// NXRR Set error
/// </summary>
NXRRSet,
/// <summary>
/// Not authorized error
/// </summary>
NotAuth,
/// <summary>
/// Not zone error
/// </summary>
NotZone,
}
#endregion
} }

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>
@ -134,167 +133,164 @@ namespace Unosquare.Swan.Networking
GeneralFailure = -1, GeneralFailure = -1,
} }
#endif #endif
/// <summary>
/// Enumerates all of the well-known SMTP command names.
/// </summary>
public enum SmtpCommandNames {
/// <summary> /// <summary>
/// Enumerates all of the well-known SMTP command names. /// An unknown command
/// </summary> /// </summary>
public enum SmtpCommandNames Unknown,
{
/// <summary>
/// An unknown command
/// </summary>
Unknown,
/// <summary>
/// The helo command
/// </summary>
HELO,
/// <summary>
/// The ehlo command
/// </summary>
EHLO,
/// <summary>
/// The quit command
/// </summary>
QUIT,
/// <summary>
/// The help command
/// </summary>
HELP,
/// <summary>
/// The noop command
/// </summary>
NOOP,
/// <summary>
/// The rset command
/// </summary>
RSET,
/// <summary>
/// The mail command
/// </summary>
MAIL,
/// <summary>
/// The data command
/// </summary>
DATA,
/// <summary>
/// The send command
/// </summary>
SEND,
/// <summary>
/// The soml command
/// </summary>
SOML,
/// <summary>
/// The saml command
/// </summary>
SAML,
/// <summary>
/// The RCPT command
/// </summary>
RCPT,
/// <summary>
/// The vrfy command
/// </summary>
VRFY,
/// <summary>
/// The expn command
/// </summary>
EXPN,
/// <summary>
/// The starttls command
/// </summary>
STARTTLS,
/// <summary>
/// The authentication command
/// </summary>
AUTH,
}
/// <summary> /// <summary>
/// Enumerates the reply code severities. /// The helo command
/// </summary> /// </summary>
public enum SmtpReplyCodeSeverities HELO,
{
/// <summary>
/// The unknown severity
/// </summary>
Unknown = 0,
/// <summary>
/// The positive completion severity
/// </summary>
PositiveCompletion = 200,
/// <summary>
/// The positive intermediate severity
/// </summary>
PositiveIntermediate = 300,
/// <summary>
/// The transient negative severity
/// </summary>
TransientNegative = 400,
/// <summary>
/// The permanent negative severity
/// </summary>
PermanentNegative = 500,
}
/// <summary> /// <summary>
/// Enumerates the reply code categories. /// The ehlo command
/// </summary> /// </summary>
public enum SmtpReplyCodeCategories EHLO,
{
/// <summary> /// <summary>
/// The unknown category /// The quit command
/// </summary> /// </summary>
Unknown = -1, QUIT,
/// <summary> /// <summary>
/// The syntax category /// The help command
/// </summary> /// </summary>
Syntax = 0, HELP,
/// <summary> /// <summary>
/// The information category /// The noop command
/// </summary> /// </summary>
Information = 1, NOOP,
/// <summary> /// <summary>
/// The connections category /// The rset command
/// </summary> /// </summary>
Connections = 2, RSET,
/// <summary> /// <summary>
/// The unspecified a category /// The mail command
/// </summary> /// </summary>
UnspecifiedA = 3, MAIL,
/// <summary> /// <summary>
/// The unspecified b category /// The data command
/// </summary> /// </summary>
UnspecifiedB = 4, DATA,
/// <summary> /// <summary>
/// The system category /// The send command
/// </summary> /// </summary>
System = 5, SEND,
}
/// <summary>
/// The soml command
/// </summary>
SOML,
/// <summary>
/// The saml command
/// </summary>
SAML,
/// <summary>
/// The RCPT command
/// </summary>
RCPT,
/// <summary>
/// The vrfy command
/// </summary>
VRFY,
/// <summary>
/// The expn command
/// </summary>
EXPN,
/// <summary>
/// The starttls command
/// </summary>
STARTTLS,
/// <summary>
/// The authentication command
/// </summary>
AUTH,
}
/// <summary>
/// Enumerates the reply code severities.
/// </summary>
public enum SmtpReplyCodeSeverities {
/// <summary>
/// The unknown severity
/// </summary>
Unknown = 0,
/// <summary>
/// The positive completion severity
/// </summary>
PositiveCompletion = 200,
/// <summary>
/// The positive intermediate severity
/// </summary>
PositiveIntermediate = 300,
/// <summary>
/// The transient negative severity
/// </summary>
TransientNegative = 400,
/// <summary>
/// The permanent negative severity
/// </summary>
PermanentNegative = 500,
}
/// <summary>
/// Enumerates the reply code categories.
/// </summary>
public enum SmtpReplyCodeCategories {
/// <summary>
/// The unknown category
/// </summary>
Unknown = -1,
/// <summary>
/// The syntax category
/// </summary>
Syntax = 0,
/// <summary>
/// The information category
/// </summary>
Information = 1,
/// <summary>
/// The connections category
/// </summary>
Connections = 2,
/// <summary>
/// The unspecified a category
/// </summary>
UnspecifiedA = 3,
/// <summary>
/// The unspecified b category
/// </summary>
UnspecifiedB = 4,
/// <summary>
/// The system category
/// </summary>
System = 5,
}
} }

View File

@ -1,400 +1,378 @@
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>
/// Represents a HttpClient with extended methods to use with JSON payloads
/// and bearer tokens authentication.
/// </summary>
public static class JsonClient {
private const String JsonMimeType = "application/json";
/// <summary> /// <summary>
/// Represents a HttpClient with extended methods to use with JSON payloads /// Post a object as JSON with optional authorization token.
/// and bearer tokens authentication.
/// </summary> /// </summary>
public static class JsonClient /// <typeparam name="T">The type of response object.</typeparam>
{ /// <param name="url">The URL.</param>
private const string JsonMimeType = "application/json"; /// <param name="payload">The payload.</param>
/// <param name="authorization">The authorization.</param>
/// <summary> /// <param name="ct">The cancellation token.</param>
/// Post a object as JSON with optional authorization token. /// <returns>A task with a result of the requested type.</returns>
/// </summary> public static async Task<T> Post<T>(
/// <typeparam name="T">The type of response object.</typeparam> String url,
/// <param name="url">The URL.</param> Object payload,
/// <param name="payload">The payload.</param> String authorization = null,
/// <param name="authorization">The authorization.</param> CancellationToken ct = default) {
/// <param name="ct">The cancellation token.</param> String jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
/// <returns>A task with a result of the requested type.</returns>
public static async Task<T> Post<T>( return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
string url, }
object payload,
string authorization = null, /// <summary>
CancellationToken ct = default) /// Posts a object as JSON with optional authorization token and retrieve an object
{ /// or an error.
var jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false); /// </summary>
/// <typeparam name="T">The type of response object.</typeparam>
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default; /// <typeparam name="TE">The type of the error.</typeparam>
} /// <param name="url">The URL.</param>
/// <param name="payload">The payload.</param>
/// <summary> /// <param name="httpStatusError">The HTTP status error.</param>
/// Posts a object as JSON with optional authorization token and retrieve an object /// <param name="authorization">The authorization.</param>
/// or an error. /// <param name="ct">The cancellation token.</param>
/// </summary> /// <returns>A task with a result of the requested type or an error object.</returns>
/// <typeparam name="T">The type of response object.</typeparam> public static async Task<OkOrError<T, TE>> PostOrError<T, TE>(
/// <typeparam name="TE">The type of the error.</typeparam> String url,
/// <param name="url">The URL.</param> Object payload,
/// <param name="payload">The payload.</param> Int32 httpStatusError = 500,
/// <param name="httpStatusError">The HTTP status error.</param> String authorization = null,
/// <param name="authorization">The authorization.</param> CancellationToken ct = default) {
/// <param name="ct">The cancellation token.</param> using(HttpClient httpClient = GetHttpClientWithAuthorizationHeader(authorization)) {
/// <returns>A task with a result of the requested type or an error object.</returns> StringContent payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
public static async Task<OkOrError<T, TE>> PostOrError<T, TE>(
string url, HttpResponseMessage response = await httpClient.PostAsync(url, payloadJson, ct).ConfigureAwait(false);
object payload,
int httpStatusError = 500, String jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
string authorization = null,
CancellationToken ct = default) return response.StatusCode == System.Net.HttpStatusCode.OK
{ ? OkOrError<T, TE>.FromOk(!String.IsNullOrEmpty(jsonString)
using (var httpClient = GetHttpClientWithAuthorizationHeader(authorization))
{
var payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
var response = await httpClient.PostAsync(url, payloadJson, ct).ConfigureAwait(false);
var jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
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>(); }
}
} /// <summary>
/// Posts the specified URL.
/// <summary> /// </summary>
/// Posts the specified URL. /// <param name="url">The URL.</param>
/// </summary> /// <param name="payload">The payload.</param>
/// <param name="url">The URL.</param> /// <param name="authorization">The authorization.</param>
/// <param name="payload">The payload.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="authorization">The authorization.</param> /// <returns>A task with a result as a collection of key/value pairs.</returns>
/// <param name="ct">The cancellation token.</param> public static async Task<IDictionary<String, Object>> Post(
/// <returns>A task with a result as a collection of key/value pairs.</returns> String url,
public static async Task<IDictionary<string, object>> Post( Object payload,
string url, String authorization = null,
object payload, CancellationToken ct = default) {
string authorization = null, String jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
CancellationToken ct = default)
{ return String.IsNullOrWhiteSpace(jsonString)
var jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false); ? default
: Json.Deserialize(jsonString) as IDictionary<String, Object>;
return string.IsNullOrWhiteSpace(jsonString) }
? default
: Json.Deserialize(jsonString) as IDictionary<string, object>; /// <summary>
} /// Posts the specified URL.
/// </summary>
/// <summary> /// <param name="url">The URL.</param>
/// Posts the specified URL. /// <param name="payload">The payload.</param>
/// </summary> /// <param name="authorization">The authorization.</param>
/// <param name="url">The URL.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="payload">The payload.</param> /// <returns>
/// <param name="authorization">The authorization.</param> /// A task with a result of the requested string.
/// <param name="ct">The cancellation token.</param> /// </returns>
/// <returns> /// <exception cref="ArgumentNullException">url.</exception>
/// A task with a result of the requested string. /// <exception cref="JsonRequestException">Error POST JSON.</exception>
/// </returns> public static Task<String> PostString(
/// <exception cref="ArgumentNullException">url.</exception> String url,
/// <exception cref="JsonRequestException">Error POST JSON.</exception> Object payload,
public static Task<string> PostString( String authorization = null,
string url, CancellationToken ct = default) => SendAsync(HttpMethod.Post, url, payload, authorization, ct);
object payload,
string authorization = null, /// <summary>
CancellationToken ct = default) => SendAsync(HttpMethod.Post, url, payload, authorization, ct); /// Puts the specified URL.
/// </summary>
/// <summary> /// <typeparam name="T">The type of response object.</typeparam>
/// Puts the specified URL. /// <param name="url">The URL.</param>
/// </summary> /// <param name="payload">The payload.</param>
/// <typeparam name="T">The type of response object.</typeparam> /// <param name="authorization">The authorization.</param>
/// <param name="url">The URL.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="payload">The payload.</param> /// <returns>A task with a result of the requested type.</returns>
/// <param name="authorization">The authorization.</param> public static async Task<T> Put<T>(
/// <param name="ct">The cancellation token.</param> String url,
/// <returns>A task with a result of the requested type.</returns> Object payload,
public static async Task<T> Put<T>( String authorization = null,
string url, CancellationToken ct = default) {
object payload, String jsonString = await PutString(url, payload, authorization, ct).ConfigureAwait(false);
string authorization = null,
CancellationToken ct = default) return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
{ }
var jsonString = await PutString(url, payload, authorization, ct).ConfigureAwait(false);
/// <summary>
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default; /// Puts the specified URL.
} /// </summary>
/// <param name="url">The URL.</param>
/// <summary> /// <param name="payload">The payload.</param>
/// Puts the specified URL. /// <param name="authorization">The authorization.</param>
/// </summary> /// <param name="ct">The cancellation token.</param>
/// <param name="url">The URL.</param> /// <returns>A task with a result of the requested collection of key/value pairs.</returns>
/// <param name="payload">The payload.</param> public static async Task<IDictionary<String, Object>> Put(
/// <param name="authorization">The authorization.</param> String url,
/// <param name="ct">The cancellation token.</param> Object payload,
/// <returns>A task with a result of the requested collection of key/value pairs.</returns> String authorization = null,
public static async Task<IDictionary<string, object>> Put( CancellationToken ct = default) {
string url, Object response = await Put<Object>(url, payload, authorization, ct).ConfigureAwait(false);
object payload,
string authorization = null, return response as IDictionary<String, Object>;
CancellationToken ct = default) }
{
var response = await Put<object>(url, payload, authorization, ct).ConfigureAwait(false); /// <summary>
/// Puts as string.
return response as IDictionary<string, object>; /// </summary>
} /// <param name="url">The URL.</param>
/// <param name="payload">The payload.</param>
/// <summary> /// <param name="authorization">The authorization.</param>
/// Puts as string. /// <param name="ct">The cancellation token.</param>
/// </summary> /// <returns>
/// <param name="url">The URL.</param> /// A task with a result of the requested string.
/// <param name="payload">The payload.</param> /// </returns>
/// <param name="authorization">The authorization.</param> /// <exception cref="ArgumentNullException">url.</exception>
/// <param name="ct">The cancellation token.</param> /// <exception cref="JsonRequestException">Error PUT JSON.</exception>
/// <returns> public static Task<String> PutString(
/// A task with a result of the requested string. String url,
/// </returns> Object payload,
/// <exception cref="ArgumentNullException">url.</exception> String authorization = null,
/// <exception cref="JsonRequestException">Error PUT JSON.</exception> CancellationToken ct = default) => SendAsync(HttpMethod.Put, url, payload, authorization, ct);
public static Task<string> PutString(
string url, /// <summary>
object payload, /// Gets as string.
string authorization = null, /// </summary>
CancellationToken ct = default) => SendAsync(HttpMethod.Put, url, payload, authorization, ct); /// <param name="url">The URL.</param>
/// <param name="authorization">The authorization.</param>
/// <summary> /// <param name="ct">The cancellation token.</param>
/// Gets as string. /// <returns>
/// </summary> /// A task with a result of the requested string.
/// <param name="url">The URL.</param> /// </returns>
/// <param name="authorization">The authorization.</param> /// <exception cref="ArgumentNullException">url.</exception>
/// <param name="ct">The cancellation token.</param> /// <exception cref="JsonRequestException">Error GET JSON.</exception>
/// <returns> public static async Task<String> GetString(
/// A task with a result of the requested string. String url,
/// </returns> String authorization = null,
/// <exception cref="ArgumentNullException">url.</exception> CancellationToken ct = default) {
/// <exception cref="JsonRequestException">Error GET JSON.</exception> HttpContent response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
public static async Task<string> GetString(
string url, return await response.ReadAsStringAsync().ConfigureAwait(false);
string authorization = null, }
CancellationToken ct = default)
{ /// <summary>
var response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false); /// Gets the specified URL and return the JSON data as object
/// with optional authorization token.
return await response.ReadAsStringAsync().ConfigureAwait(false); /// </summary>
} /// <typeparam name="T">The response type.</typeparam>
/// <param name="url">The URL.</param>
/// <summary> /// <param name="authorization">The authorization.</param>
/// Gets the specified URL and return the JSON data as object /// <param name="ct">The cancellation token.</param>
/// with optional authorization token. /// <returns>A task with a result of the requested type.</returns>
/// </summary> public static async Task<T> Get<T>(
/// <typeparam name="T">The response type.</typeparam> String url,
/// <param name="url">The URL.</param> String authorization = null,
/// <param name="authorization">The authorization.</param> CancellationToken ct = default) {
/// <param name="ct">The cancellation token.</param> String jsonString = await GetString(url, authorization, ct).ConfigureAwait(false);
/// <returns>A task with a result of the requested type.</returns>
public static async Task<T> Get<T>( return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
string url, }
string authorization = null,
CancellationToken ct = default) /// <summary>
{ /// Gets the binary.
var jsonString = await GetString(url, authorization, ct).ConfigureAwait(false); /// </summary>
/// <param name="url">The URL.</param>
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default; /// <param name="authorization">The authorization.</param>
} /// <param name="ct">The cancellation token.</param>
/// <returns>
/// <summary> /// A task with a result of the requested byte array.
/// Gets the binary. /// </returns>
/// </summary> /// <exception cref="ArgumentNullException">url.</exception>
/// <param name="url">The URL.</param> /// <exception cref="JsonRequestException">Error GET Binary.</exception>
/// <param name="authorization">The authorization.</param> public static async Task<Byte[]> GetBinary(
/// <param name="ct">The cancellation token.</param> String url,
/// <returns> String authorization = null,
/// A task with a result of the requested byte array. CancellationToken ct = default) {
/// </returns> HttpContent response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
/// <exception cref="ArgumentNullException">url.</exception>
/// <exception cref="JsonRequestException">Error GET Binary.</exception> return await response.ReadAsByteArrayAsync().ConfigureAwait(false);
public static async Task<byte[]> GetBinary( }
string url,
string authorization = null, /// <summary>
CancellationToken ct = default) /// Authenticate against a web server using Bearer Token.
{ /// </summary>
var response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false); /// <param name="url">The URL.</param>
/// <param name="username">The username.</param>
return await response.ReadAsByteArrayAsync().ConfigureAwait(false); /// <param name="password">The password.</param>
} /// <param name="ct">The cancellation token.</param>
/// <returns>
/// <summary> /// A task with a Dictionary with authentication data.
/// Authenticate against a web server using Bearer Token. /// </returns>
/// </summary> /// <exception cref="ArgumentNullException">
/// <param name="url">The URL.</param> /// url
/// <param name="username">The username.</param> /// or
/// <param name="password">The password.</param> /// username.
/// <param name="ct">The cancellation token.</param> /// </exception>
/// <returns> /// <exception cref="SecurityException">Error Authenticating.</exception>
/// A task with a Dictionary with authentication data. public static async Task<IDictionary<String, Object>> Authenticate(
/// </returns> String url,
/// <exception cref="ArgumentNullException"> String username,
/// url String password,
/// or CancellationToken ct = default) {
/// username. if(String.IsNullOrWhiteSpace(url)) {
/// </exception> throw new ArgumentNullException(nameof(url));
/// <exception cref="SecurityException">Error Authenticating.</exception> }
public static async Task<IDictionary<string, object>> Authenticate(
string url, if(String.IsNullOrWhiteSpace(username)) {
string username, throw new ArgumentNullException(nameof(username));
string password, }
CancellationToken ct = default)
{ using(HttpClient httpClient = new HttpClient()) {
if (string.IsNullOrWhiteSpace(url)) // ignore empty password for now
throw new ArgumentNullException(nameof(url)); StringContent requestContent = new StringContent(
if (string.IsNullOrWhiteSpace(username))
throw new ArgumentNullException(nameof(username));
using (var httpClient = new HttpClient())
{
// ignore empty password for now
var 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>;
} }
}
/// <summary>
/// Posts the file. /// <summary>
/// </summary> /// Posts the file.
/// <param name="url">The URL.</param> /// </summary>
/// <param name="buffer">The buffer.</param> /// <param name="url">The URL.</param>
/// <param name="fileName">Name of the file.</param> /// <param name="buffer">The buffer.</param>
/// <param name="authorization">The authorization.</param> /// <param name="fileName">Name of the file.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="authorization">The authorization.</param>
/// <returns> /// <param name="ct">The cancellation token.</param>
/// A task with a result of the requested string. /// <returns>
/// </returns> /// A task with a result of the requested string.
public static Task<string> PostFileString( /// </returns>
string url, public static Task<String> PostFileString(
byte[] buffer, String url,
string fileName, Byte[] buffer,
string authorization = null, String fileName,
CancellationToken ct = default) String authorization = null,
{ CancellationToken ct = default) => PostString(url, new {
return PostString(url, new {Filename = fileName, Data = buffer}, authorization, ct); Filename = fileName, Data = buffer
} }, authorization, ct);
/// <summary> /// <summary>
/// Posts the file. /// Posts the file.
/// </summary> /// </summary>
/// <typeparam name="T">The response type.</typeparam> /// <typeparam name="T">The response type.</typeparam>
/// <param name="url">The URL.</param> /// <param name="url">The URL.</param>
/// <param name="buffer">The buffer.</param> /// <param name="buffer">The buffer.</param>
/// <param name="fileName">Name of the file.</param> /// <param name="fileName">Name of the file.</param>
/// <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 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. /// </summary>
/// </summary> /// <param name="method">The method.</param>
/// <param name="method">The method.</param> /// <param name="url">The URL.</param>
/// <param name="url">The URL.</param> /// <param name="payload">The payload.</param>
/// <param name="payload">The payload.</param> /// <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)) {
{ throw new ArgumentNullException(nameof(url));
if (string.IsNullOrWhiteSpace(url)) }
throw new ArgumentNullException(nameof(url));
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); HttpResponseMessage response = await httpClient
.SendAsync(new HttpRequestMessage(method, url) { Content = payloadJson }, ct).ConfigureAwait(false);
var response = await httpClient
.SendAsync(new HttpRequestMessage(method, url) {Content = payloadJson}, ct).ConfigureAwait(false); if(response.IsSuccessStatusCode == false) {
throw new JsonRequestException(
if (response.IsSuccessStatusCode == false) $"Error {method} JSON",
{ (Int32)response.StatusCode,
throw new JsonRequestException( await response.Content.ReadAsStringAsync().ConfigureAwait(false));
$"Error {method} JSON", }
(int) response.StatusCode,
await response.Content.ReadAsStringAsync().ConfigureAwait(false)); return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
} }
}
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
} private static HttpClient GetHttpClientWithAuthorizationHeader(String authorization) {
} HttpClient httpClient = new HttpClient();
private static HttpClient GetHttpClientWithAuthorizationHeader(string authorization) if(String.IsNullOrWhiteSpace(authorization) == false) {
{ httpClient.DefaultRequestHeaders.Authorization =
var httpClient = new HttpClient(); new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authorization);
}
if (string.IsNullOrWhiteSpace(authorization) == false)
{ return httpClient;
httpClient.DefaultRequestHeaders.Authorization = }
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authorization);
} private static async Task<HttpContent> GetHttpContent(
String url,
return httpClient; String authorization,
} CancellationToken ct) {
if(String.IsNullOrWhiteSpace(url)) {
private static async Task<HttpContent> GetHttpContent( throw new ArgumentNullException(nameof(url));
string url, }
string authorization,
CancellationToken ct) using(HttpClient httpClient = GetHttpClientWithAuthorizationHeader(authorization)) {
{ HttpResponseMessage response = await httpClient.GetAsync(url, ct).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url)); if(response.IsSuccessStatusCode == false) {
throw new JsonRequestException("Error GET", (Int32)response.StatusCode);
using (var httpClient = GetHttpClientWithAuthorizationHeader(authorization)) }
{
var response = await httpClient.GetAsync(url, ct).ConfigureAwait(false); return response.Content;
}
if (response.IsSuccessStatusCode == false) }
throw new JsonRequestException("Error GET", (int) response.StatusCode); }
return response.Content;
}
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,166 +1,165 @@
namespace Unosquare.Swan.Networking.Ldap using System.IO;
{ using System;
using System.IO;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// 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:
/// 5.1. Mapping Onto BER-based Transport Services
/// The protocol elements of Ldap are encoded for exchange using the
/// Basic Encoding Rules (BER) [11] of ASN.1 [3]. However, due to the
/// high overhead involved in using certain elements of the BER, the
/// following additional restrictions are placed on BER-encodings of Ldap
/// protocol elements:
/// <li>(1) Only the definite form of length encoding will be used.</li>
/// <li>(2) OCTET STRING values will be encoded in the primitive form only.</li><li>
/// (3) If the value of a BOOLEAN type is true, the encoding MUST have
/// its contents octets set to hex "FF".
/// </li><li>
/// (4) If a value of a type is its default value, it MUST be absent.
/// Only some BOOLEAN and INTEGER types have default values in this
/// protocol definition.
/// These restrictions do not apply to ASN.1 types encapsulated inside of
/// OCTET STRING values, such as attribute values, unless otherwise
/// noted.
/// </li>
/// [3] ITU-T Rec. X.680, "Abstract Syntax Notation One (ASN.1) -
/// Specification of Basic Notation", 1994.
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
/// Canonical, and Distinguished Encoding Rules", 1994.
/// </summary>
internal static class LberDecoder {
/// <summary> /// <summary>
/// This class provides LBER decoding routines for ASN.1 Types. LBER is a /// Decode an LBER encoded value into an Asn1Object from an InputStream.
/// subset of BER as described in the following taken from 5.1 of RFC 2251: /// This method also returns the total length of this encoded
/// 5.1. Mapping Onto BER-based Transport Services /// Asn1Object (length of type + length of length + length of content)
/// The protocol elements of Ldap are encoded for exchange using the /// in the parameter len. This information is helpful when decoding
/// Basic Encoding Rules (BER) [11] of ASN.1 [3]. However, due to the /// structured types.
/// high overhead involved in using certain elements of the BER, the
/// following additional restrictions are placed on BER-encodings of Ldap
/// protocol elements:
/// <li>(1) Only the definite form of length encoding will be used.</li>
/// <li>(2) OCTET STRING values will be encoded in the primitive form only.</li><li>
/// (3) If the value of a BOOLEAN type is true, the encoding MUST have
/// its contents octets set to hex "FF".
/// </li><li>
/// (4) If a value of a type is its default value, it MUST be absent.
/// Only some BOOLEAN and INTEGER types have default values in this
/// protocol definition.
/// These restrictions do not apply to ASN.1 types encapsulated inside of
/// OCTET STRING values, such as attribute values, unless otherwise
/// noted.
/// </li>
/// [3] ITU-T Rec. X.680, "Abstract Syntax Notation One (ASN.1) -
/// Specification of Basic Notation", 1994.
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
/// Canonical, and Distinguished Encoding Rules", 1994.
/// </summary> /// </summary>
internal static class LberDecoder /// <param name="stream">The stream.</param>
{ /// <param name="len">The length.</param>
/// <summary> /// <returns>
/// Decode an LBER encoded value into an Asn1Object from an InputStream. /// Decoded Asn1Obect.
/// This method also returns the total length of this encoded /// </returns>
/// Asn1Object (length of type + length of length + length of content) /// <exception cref="EndOfStreamException">Unknown tag.</exception>
/// in the parameter len. This information is helpful when decoding public static Asn1Object Decode(Stream stream, Int32[] len) {
/// structured types. Asn1Identifier asn1Id = new Asn1Identifier(stream);
/// </summary> Asn1Length asn1Len = new Asn1Length(stream);
/// <param name="stream">The stream.</param>
/// <param name="len">The length.</param> Int32 length = asn1Len.Length;
/// <returns> len[0] = asn1Id.EncodedLength + asn1Len.EncodedLength + length;
/// Decoded Asn1Obect.
/// </returns> if(asn1Id.Universal == false) {
/// <exception cref="EndOfStreamException">Unknown tag.</exception> return new Asn1Tagged(stream, length, (Asn1Identifier)asn1Id.Clone());
public static Asn1Object Decode(Stream stream, int[] len) }
{
var asn1Id = new Asn1Identifier(stream); switch(asn1Id.Tag) {
var asn1Len = new Asn1Length(stream); case Asn1Sequence.Tag:
return new Asn1Sequence(stream, length);
var length = asn1Len.Length;
len[0] = asn1Id.EncodedLength + asn1Len.EncodedLength + length; case Asn1Set.Tag:
return new Asn1Set(stream, length);
if (asn1Id.Universal == false)
return new Asn1Tagged(stream, length, (Asn1Identifier) asn1Id.Clone()); case Asn1Boolean.Tag:
return new Asn1Boolean(stream, length);
switch (asn1Id.Tag)
{ case Asn1Integer.Tag:
case Asn1Sequence.Tag: return new Asn1Integer(stream, length);
return new Asn1Sequence(stream, length);
case Asn1OctetString.Tag:
case Asn1Set.Tag: return new Asn1OctetString(stream, length);
return new Asn1Set(stream, length);
case Asn1Enumerated.Tag:
case Asn1Boolean.Tag: return new Asn1Enumerated(stream, length);
return new Asn1Boolean(stream, length);
case Asn1Null.Tag:
case Asn1Integer.Tag: return new Asn1Null(); // has no content to decode.
return new Asn1Integer(stream, length);
default:
case Asn1OctetString.Tag: throw new EndOfStreamException("Unknown tag");
return new Asn1OctetString(stream, length); }
}
case Asn1Enumerated.Tag:
return new Asn1Enumerated(stream, length); /// <summary>
/// Decode a boolean directly from a stream.
case Asn1Null.Tag: /// </summary>
return new Asn1Null(); // has no content to decode. /// <param name="stream">The stream.</param>
/// <param name="len">Length in bytes.</param>
default: /// <returns>
throw new EndOfStreamException("Unknown tag"); /// Decoded boolean object.
} /// </returns>
} /// <exception cref="EndOfStreamException">LBER: BOOLEAN: decode error: EOF.</exception>
public static Boolean DecodeBoolean(Stream stream, Int32 len) {
/// <summary> SByte[] lber = new SByte[len];
/// Decode a boolean directly from a stream.
/// </summary> if(stream.ReadInput(ref lber, 0, lber.Length) != len) {
/// <param name="stream">The stream.</param> throw new EndOfStreamException("LBER: BOOLEAN: decode error: EOF");
/// <param name="len">Length in bytes.</param> }
/// <returns>
/// Decoded boolean object. return lber[0] != 0x00;
/// </returns> }
/// <exception cref="EndOfStreamException">LBER: BOOLEAN: decode error: EOF.</exception>
public static bool DecodeBoolean(Stream stream, int len) /// <summary>
{ /// Decode a Numeric type directly from a stream. Decodes INTEGER
var lber = new sbyte[len]; /// and ENUMERATED types.
/// </summary>
if (stream.ReadInput(ref lber, 0, lber.Length) != len) /// <param name="stream">The stream.</param>
throw new EndOfStreamException("LBER: BOOLEAN: decode error: EOF"); /// <param name="len">Length in bytes.</param>
/// <returns>
return lber[0] != 0x00; /// Decoded numeric object.
} /// </returns>
/// <exception cref="EndOfStreamException">
/// <summary> /// LBER: NUMERIC: decode error: EOF
/// Decode a Numeric type directly from a stream. Decodes INTEGER /// or
/// and ENUMERATED types. /// LBER: NUMERIC: decode error: EOF.
/// </summary> /// </exception>
/// <param name="stream">The stream.</param> public static Int64 DecodeNumeric(Stream stream, Int32 len) {
/// <param name="len">Length in bytes.</param> Int64 l = 0;
/// <returns> Int32 r = stream.ReadByte();
/// Decoded numeric object.
/// </returns> if(r < 0) {
/// <exception cref="EndOfStreamException"> throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
/// LBER: NUMERIC: decode error: EOF }
/// or
/// LBER: NUMERIC: decode error: EOF. if((r & 0x80) != 0) {
/// </exception> // check for negative number
public static long DecodeNumeric(Stream stream, int len) l = -1;
{ }
long l = 0;
var r = stream.ReadByte(); #pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
l = (l << 8) | r;
if (r < 0) #pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
for(Int32 i = 1; i < len; i++) {
if ((r & 0x80) != 0) r = stream.ReadByte();
{ if(r < 0) {
// check for negative number throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
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++) }
{
r = stream.ReadByte(); return l;
if (r < 0) }
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
/// <summary>
l = (l << 8) | r; /// Decode an OctetString directly from a stream.
} /// </summary>
/// <param name="stream">The stream.</param>
return l; /// <param name="len">Length in bytes.</param>
} /// <returns>Decoded octet. </returns>
public static Object DecodeOctetString(Stream stream, Int32 len) {
/// <summary> SByte[] octets = new SByte[len];
/// Decode an OctetString directly from a stream. Int32 totalLen = 0;
/// </summary>
/// <param name="stream">The stream.</param> while(totalLen < len) {
/// <param name="len">Length in bytes.</param> // Make sure we have read all the data
/// <returns>Decoded octet. </returns> totalLen += stream.ReadInput(ref octets, totalLen, len - totalLen);
public static object DecodeOctetString(Stream stream, int len) }
{
var octets = new sbyte[len]; return octets;
var totalLen = 0; }
}
while (totalLen < len)
{
// Make sure we have read all the data
totalLen += stream.ReadInput(ref octets, totalLen, len - totalLen);
}
return octets;
}
}
} }

View File

@ -1,246 +1,223 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.IO;
using System.IO;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// 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:
/// 5.1. Mapping Onto BER-based Transport Services
/// The protocol elements of Ldap are encoded for exchange using the
/// Basic Encoding Rules (BER) [11] of ASN.1 [3]. However, due to the
/// high overhead involved in using certain elements of the BER, the
/// following additional restrictions are placed on BER-encodings of Ldap
/// protocol elements:
/// <li>(1) Only the definite form of length encoding will be used.</li>
/// <li>(2) OCTET STRING values will be encoded in the primitive form only.</li><li>
/// (3) If the value of a BOOLEAN type is true, the encoding MUST have
/// its contents octets set to hex "FF".
/// </li><li>
/// (4) If a value of a type is its default value, it MUST be absent.
/// Only some BOOLEAN and INTEGER types have default values in this
/// protocol definition.
/// These restrictions do not apply to ASN.1 types encapsulated inside of
/// OCTET STRING values, such as attribute values, unless otherwise
/// noted.
/// </li>
/// [3] ITU-T Rec. X.680, "Abstract Syntax Notation One (ASN.1) -
/// Specification of Basic Notation", 1994.
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
/// Canonical, and Distinguished Encoding Rules", 1994.
/// </summary>
internal static class LberEncoder {
/// <summary> /// <summary>
/// This class provides LBER encoding routines for ASN.1 Types. LBER is a /// BER Encode an Asn1Boolean directly into the specified output stream.
/// subset of BER as described in the following taken from 5.1 of RFC 2251:
/// 5.1. Mapping Onto BER-based Transport Services
/// The protocol elements of Ldap are encoded for exchange using the
/// Basic Encoding Rules (BER) [11] of ASN.1 [3]. However, due to the
/// high overhead involved in using certain elements of the BER, the
/// following additional restrictions are placed on BER-encodings of Ldap
/// protocol elements:
/// <li>(1) Only the definite form of length encoding will be used.</li>
/// <li>(2) OCTET STRING values will be encoded in the primitive form only.</li><li>
/// (3) If the value of a BOOLEAN type is true, the encoding MUST have
/// its contents octets set to hex "FF".
/// </li><li>
/// (4) If a value of a type is its default value, it MUST be absent.
/// Only some BOOLEAN and INTEGER types have default values in this
/// protocol definition.
/// These restrictions do not apply to ASN.1 types encapsulated inside of
/// OCTET STRING values, such as attribute values, unless otherwise
/// noted.
/// </li>
/// [3] ITU-T Rec. X.680, "Abstract Syntax Notation One (ASN.1) -
/// Specification of Basic Notation", 1994.
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
/// Canonical, and Distinguished Encoding Rules", 1994.
/// </summary> /// </summary>
internal static class LberEncoder /// <param name="b">The Asn1Boolean object to encode.</param>
{ /// <param name="stream">The stream.</param>
/// <summary> public static void Encode(Asn1Boolean b, Stream stream) {
/// BER Encode an Asn1Boolean directly into the specified output stream. Encode(b.GetIdentifier(), stream);
/// </summary> stream.WriteByte(0x01);
/// <param name="b">The Asn1Boolean object to encode.</param> stream.WriteByte((Byte)(b.BooleanValue() ? 0xff : 0x00));
/// <param name="stream">The stream.</param> }
public static void Encode(Asn1Boolean b, Stream stream)
{ /// <summary>
Encode(b.GetIdentifier(), stream); /// Encode an Asn1Numeric directly into the specified outputstream.
stream.WriteByte(0x01); /// Use a two's complement representation in the fewest number of octets
stream.WriteByte((byte) (b.BooleanValue() ? 0xff : 0x00)); /// possible.
} /// Can be used to encode INTEGER and ENUMERATED values.
/// </summary>
/// <summary> /// <param name="n">The Asn1Numeric object to encode.</param>
/// Encode an Asn1Numeric directly into the specified outputstream. /// <param name="stream">The stream.</param>
/// Use a two's complement representation in the fewest number of octets public static void Encode(Asn1Numeric n, Stream stream) {
/// possible. SByte[] octets = new SByte[8];
/// Can be used to encode INTEGER and ENUMERATED values. SByte len;
/// </summary> Int64 longValue = n.LongValue();
/// <param name="n">The Asn1Numeric object to encode.</param> Int64 endValue = longValue < 0 ? -1 : 0;
/// <param name="stream">The stream.</param> Int64 endSign = endValue & 0x80;
public static void Encode(Asn1Numeric n, Stream stream)
{ for(len = 0; len == 0 || longValue != endValue || (octets[len - 1] & 0x80) != endSign; len++) {
var octets = new sbyte[8]; octets[len] = (SByte)(longValue & 0xFF);
sbyte len; longValue >>= 8;
var longValue = n.LongValue(); }
long endValue = longValue < 0 ? -1 : 0;
var endSign = endValue & 0x80; Encode(n.GetIdentifier(), stream);
stream.WriteByte((Byte)len);
for (len = 0; len == 0 || longValue != endValue || (octets[len - 1] & 0x80) != endSign; len++)
{ for(Int32 i = len - 1; i >= 0; i--) {
octets[len] = (sbyte)(longValue & 0xFF); stream.WriteByte((Byte)octets[i]);
longValue >>= 8; }
} }
Encode(n.GetIdentifier(), stream); /// <summary>
stream.WriteByte((byte)len); /// Encode an Asn1OctetString directly into the specified outputstream.
/// </summary>
for (var i = len - 1; i >= 0; i--) /// <param name="os">The Asn1OctetString object to encode.</param>
{ /// <param name="stream">The stream.</param>
stream.WriteByte((byte) octets[i]); public static void Encode(Asn1OctetString os, Stream stream) {
} Encode(os.GetIdentifier(), stream);
} EncodeLength(os.ByteValue().Length, stream);
SByte[] tempSbyteArray = os.ByteValue();
/// <summary> stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
/// Encode an Asn1OctetString directly into the specified outputstream. }
/// </summary>
/// <param name="os">The Asn1OctetString object to encode.</param> public static void Encode(Asn1Object obj, Stream stream) {
/// <param name="stream">The stream.</param> switch(obj) {
public static void Encode(Asn1OctetString os, Stream stream) case Asn1Boolean b:
{ Encode(b, stream);
Encode(os.GetIdentifier(), stream); break;
EncodeLength(os.ByteValue().Length, stream); case Asn1Numeric n:
var tempSbyteArray = os.ByteValue(); Encode(n, stream);
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length); break;
} case Asn1Null n:
Encode(n.GetIdentifier(), stream);
public static void Encode(Asn1Object obj, Stream stream) stream.WriteByte(0x00); // Length (with no Content)
{ break;
switch (obj) case Asn1OctetString n:
{ Encode(n, stream);
case Asn1Boolean b: break;
Encode(b, stream); case Asn1Structured n:
break; Encode(n, stream);
case Asn1Numeric n: break;
Encode(n, stream); case Asn1Tagged n:
break; Encode(n, stream);
case Asn1Null n: break;
Encode(n.GetIdentifier(), stream); case Asn1Choice n:
stream.WriteByte(0x00); // Length (with no Content) Encode(n.ChoiceValue, stream);
break; break;
case Asn1OctetString n: default:
Encode(n, stream); throw new InvalidDataException();
break; }
case Asn1Structured n: }
Encode(n, stream);
break; /// <summary>
case Asn1Tagged n: /// Encode an Asn1Structured into the specified outputstream. This method
Encode(n, stream); /// can be used to encode SET, SET_OF, SEQUENCE, SEQUENCE_OF.
break; /// </summary>
case Asn1Choice n: /// <param name="c">The Asn1Structured object to encode.</param>
Encode(n.ChoiceValue, stream); /// <param name="stream">The stream.</param>
break; public static void Encode(Asn1Structured c, Stream stream) {
default: Encode(c.GetIdentifier(), stream);
throw new InvalidDataException();
} Asn1Object[] arrayValue = c.ToArray();
}
using(MemoryStream output = new MemoryStream()) {
/// <summary> foreach(Asn1Object obj in arrayValue) {
/// Encode an Asn1Structured into the specified outputstream. This method Encode(obj, output);
/// can be used to encode SET, SET_OF, SEQUENCE, SEQUENCE_OF. }
/// </summary>
/// <param name="c">The Asn1Structured object to encode.</param> EncodeLength((Int32)output.Length, stream);
/// <param name="stream">The stream.</param>
public static void Encode(Asn1Structured c, Stream stream) Byte[] tempSbyteArray = output.ToArray();
{ stream.Write(tempSbyteArray, 0, tempSbyteArray.Length);
Encode(c.GetIdentifier(), stream); }
}
var arrayValue = c.ToArray();
/// <summary>
using (var output = new MemoryStream()) /// Encode an Asn1Tagged directly into the specified outputstream.
{ /// </summary>
foreach (var obj in arrayValue) /// <param name="t">The Asn1Tagged object to encode.</param>
{ /// <param name="stream">The stream.</param>
Encode(obj, output); public static void Encode(Asn1Tagged t, Stream stream) {
} if(!t.Explicit) {
Encode(t.TaggedValue, stream);
EncodeLength((int) output.Length, stream); return;
}
var tempSbyteArray = output.ToArray();
stream.Write(tempSbyteArray, 0, tempSbyteArray.Length); Encode(t.GetIdentifier(), stream);
}
} // determine the encoded length of the base type.
using(MemoryStream encodedContent = new MemoryStream()) {
/// <summary> Encode(t.TaggedValue, encodedContent);
/// Encode an Asn1Tagged directly into the specified outputstream.
/// </summary> EncodeLength((Int32)encodedContent.Length, stream);
/// <param name="t">The Asn1Tagged object to encode.</param> SByte[] tempSbyteArray = encodedContent.ToArray().ToSByteArray();
/// <param name="stream">The stream.</param> stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
public static void Encode(Asn1Tagged t, Stream stream) }
{ }
if (!t.Explicit)
{ /// <summary>
Encode(t.TaggedValue, stream); /// Encode an Asn1Identifier directly into the specified outputstream.
return; /// </summary>
} /// <param name="id">The Asn1Identifier object to encode.</param>
/// <param name="stream">The stream.</param>
Encode(t.GetIdentifier(), stream); public static void Encode(Asn1Identifier id, Stream stream) {
Int32 c = (Int32)id.Asn1Class;
// determine the encoded length of the base type. Int32 t = id.Tag;
using (var encodedContent = new MemoryStream()) SByte ccf = (SByte)((c << 6) | (id.Constructed ? 0x20 : 0));
{
Encode(t.TaggedValue, encodedContent); if(t < 30) {
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
EncodeLength((int) encodedContent.Length, stream); stream.WriteByte((Byte)(ccf | t));
var tempSbyteArray = encodedContent.ToArray().ToSByteArray(); #pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length); } else {
} stream.WriteByte((Byte)(ccf | 0x1F));
} EncodeTagInteger(t, stream);
}
/// <summary> }
/// Encode an Asn1Identifier directly into the specified outputstream.
/// </summary> /// <summary>
/// <param name="id">The Asn1Identifier object to encode.</param> /// Encodes the length.
/// <param name="stream">The stream.</param> /// </summary>
public static void Encode(Asn1Identifier id, Stream stream) /// <param name="length">The length.</param>
{ /// <param name="stream">The stream.</param>
var c = (int) id.Asn1Class; private static void EncodeLength(Int32 length, Stream stream) {
var t = id.Tag; if(length < 0x80) {
var ccf = (sbyte)((c << 6) | (id.Constructed ? 0x20 : 0)); stream.WriteByte((Byte)length);
} else {
if (t < 30) SByte[] octets = new SByte[4]; // 4 bytes sufficient for 32 bit int.
{ SByte n;
stream.WriteByte((byte)(ccf | t)); for(n = 0; length != 0; n++) {
} octets[n] = (SByte)(length & 0xFF);
else length >>= 8;
{ }
stream.WriteByte((byte)(ccf | 0x1F));
EncodeTagInteger(t, stream); stream.WriteByte((Byte)(0x80 | n));
}
} for(Int32 i = n - 1; i >= 0; i--) {
stream.WriteByte((Byte)octets[i]);
/// <summary> }
/// Encodes the length. }
/// </summary> }
/// <param name="length">The length.</param>
/// <param name="stream">The stream.</param> /// <summary>
private static void EncodeLength(int length, Stream stream) /// Encodes the provided tag into the stream.
{ /// </summary>
if (length < 0x80) /// <param name="val">The value.</param>
{ /// <param name="stream">The stream.</param>
stream.WriteByte((byte)length); private static void EncodeTagInteger(Int32 val, Stream stream) {
} SByte[] octets = new SByte[5];
else Int32 n;
{
var octets = new sbyte[4]; // 4 bytes sufficient for 32 bit int. for(n = 0; val != 0; n++) {
sbyte n; octets[n] = (SByte)(val & 0x7F);
for (n = 0; length != 0; n++) val >>= 7;
{ }
octets[n] = (sbyte)(length & 0xFF);
length >>= 8; for(Int32 i = n - 1; i > 0; i--) {
} stream.WriteByte((Byte)(octets[i] | 0x80));
}
stream.WriteByte((byte)(0x80 | n));
stream.WriteByte((Byte)octets[0]);
for (var i = n - 1; i >= 0; i--) }
stream.WriteByte((byte)octets[i]); }
}
}
/// <summary>
/// Encodes the provided tag into the stream.
/// </summary>
/// <param name="val">The value.</param>
/// <param name="stream">The stream.</param>
private static void EncodeTagInteger(int val, Stream stream)
{
var octets = new sbyte[5];
int n;
for (n = 0; val != 0; n++)
{
octets[n] = (sbyte)(val & 0x7F);
val = val >> 7;
}
for (var i = n - 1; i > 0; i--)
{
stream.WriteByte((byte)(octets[i] | 0x80));
}
stream.WriteByte((byte)octets[0]);
}
}
} }

View File

@ -1,402 +1,382 @@
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>
/// The central class that encapsulates the connection
/// to a directory server through the Ldap protocol.
/// LdapConnection objects are used to perform common Ldap
/// operations such as search, modify and add.
/// In addition, LdapConnection objects allow you to bind to an
/// Ldap server, set connection and search constraints, and perform
/// several other tasks.
/// An LdapConnection object is not connected on
/// construction and can only be connected to one server at one
/// port.
///
/// Based on https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard.
/// </summary>
/// <example>
/// The following code describes how to use the LdapConnection class:
///
/// <code>
/// class Example
/// {
/// using Unosquare.Swan;
/// using Unosquare.Swan.Networking.Ldap;
/// using System.Threading.Tasks;
///
/// static async Task Main()
/// {
/// // create a LdapConnection object
/// var connection = new LdapConnection();
///
/// // connect to a server
/// await connection.Connect("ldap.forumsys.com", 389);
///
/// // set up the credentials
/// await connection.Bind("cn=read-only-admin,dc=example,dc=com", "password");
///
/// // retrieve all entries that have the specified email using ScopeSub
/// // which searches all entries at all levels under
/// // and including the specified base DN
/// var searchResult = await connection
/// .Search("dc=example,dc=com", LdapConnection.ScopeSub, "(cn=Isaac Newton)");
///
/// // if there are more entries remaining keep going
/// while (searchResult.HasMore())
/// {
/// // point to the next entry
/// var entry = searchResult.Next();
///
/// // get all attributes
/// var entryAttributes = entry.GetAttributeSet();
///
/// // select its name and print it out
/// entryAttributes.GetAttribute("cn").StringValue.Info();
/// }
///
/// // modify Tesla and sets its email as tesla@email.com
/// connection.Modify("uid=tesla,dc=example,dc=com",
/// new[] {
/// new LdapModification(LdapModificationOp.Replace,
/// "mail", "tesla@email.com")
/// });
///
/// // delete the listed values from the given attribute
/// connection.Modify("uid=tesla,dc=example,dc=com",
/// new[] {
/// new LdapModification(LdapModificationOp.Delete,
/// "mail", "tesla@email.com")
/// });
///
/// // add back the recently deleted property
/// connection.Modify("uid=tesla,dc=example,dc=com",
/// new[] {
/// new LdapModification(LdapModificationOp.Add,
/// "mail", "tesla@email.com")
/// });
///
/// // disconnect from the LDAP server
/// connection.Disconnect();
///
/// Terminal.Flush();
/// }
/// }
/// </code>
/// </example>
public class LdapConnection : IDisposable {
private const Int32 LdapV3 = 3;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0069:Verwerfbare Felder verwerfen", Justification = "<Ausstehend>")]
private Connection _conn;
private Boolean _isDisposing;
/// <summary> /// <summary>
/// The central class that encapsulates the connection /// Returns the protocol version uses to authenticate.
/// to a directory server through the Ldap protocol. /// 0 is returned if no authentication has been performed.
/// LdapConnection objects are used to perform common Ldap
/// operations such as search, modify and add.
/// In addition, LdapConnection objects allow you to bind to an
/// Ldap server, set connection and search constraints, and perform
/// several other tasks.
/// An LdapConnection object is not connected on
/// construction and can only be connected to one server at one
/// port.
///
/// Based on https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard.
/// </summary> /// </summary>
/// <example> /// <value>
/// The following code describes how to use the LdapConnection class: /// The protocol version.
/// /// </value>
/// <code> public Int32 ProtocolVersion => this.BindProperties?.ProtocolVersion ?? LdapV3;
/// class Example
/// { /// <summary>
/// using Unosquare.Swan; /// Returns the distinguished name (DN) used for as the bind name during
/// using Unosquare.Swan.Networking.Ldap; /// the last successful bind operation. null is returned
/// using System.Threading.Tasks; /// if no authentication has been performed or if the bind resulted in
/// /// an anonymous connection.
/// static async Task Main() /// </summary>
/// { /// <value>
/// // create a LdapConnection object /// The authentication dn.
/// var connection = new LdapConnection(); /// </value>
/// public String AuthenticationDn => this.BindProperties == null ? null : (this.BindProperties.Anonymous ? null : this.BindProperties.AuthenticationDN);
/// // connect to a server
/// await connection.Connect("ldap.forumsys.com", 389); /// <summary>
/// /// Returns the method used to authenticate the connection. The return
/// // set up the credentials /// value is one of the following:.
/// await connection.Bind("cn=read-only-admin,dc=example,dc=com", "password"); /// <ul><li>"none" indicates the connection is not authenticated.</li><li>
/// /// "simple" indicates simple authentication was used or that a null
/// // retrieve all entries that have the specified email using ScopeSub /// or empty authentication DN was specified.
/// // which searches all entries at all levels under /// </li><li>"sasl" indicates that a SASL mechanism was used to authenticate</li></ul>
/// // and including the specified base DN /// </summary>
/// var searchResult = await connection /// <value>
/// .Search("dc=example,dc=com", LdapConnection.ScopeSub, "(cn=Isaac Newton)"); /// The authentication method.
/// /// </value>
/// // if there are more entries remaining keep going public String AuthenticationMethod => this.BindProperties == null ? "simple" : this.BindProperties.AuthenticationMethod;
/// while (searchResult.HasMore())
/// { /// <summary>
/// // point to the next entry /// Indicates whether the connection represented by this object is open
/// var entry = searchResult.Next(); /// at this time.
/// /// </summary>
/// // get all attributes /// <returns>
/// var entryAttributes = entry.GetAttributeSet(); /// True if connection is open; false if the connection is closed.
/// /// </returns>
/// // select its name and print it out public Boolean Connected => this._conn?.IsConnected == true;
/// entryAttributes.GetAttribute("cn").StringValue.Info();
/// } internal BindProperties BindProperties {
/// get; set;
/// // modify Tesla and sets its email as tesla@email.com }
/// connection.Modify("uid=tesla,dc=example,dc=com",
/// new[] { internal List<RfcLdapMessage> Messages { get; } = new List<RfcLdapMessage>();
/// new LdapModification(LdapModificationOp.Replace,
/// "mail", "tesla@email.com") /// <inheritdoc />
/// }); public void Dispose() {
/// if(this._isDisposing) {
/// // delete the listed values from the given attribute return;
/// connection.Modify("uid=tesla,dc=example,dc=com", }
/// new[] {
/// new LdapModification(LdapModificationOp.Delete, this._isDisposing = true;
/// "mail", "tesla@email.com") this.Disconnect();
/// }); this._cts?.Dispose();
/// }
/// // add back the recently deleted property
/// connection.Modify("uid=tesla,dc=example,dc=com", /// <summary>
/// new[] { /// Synchronously authenticates to the Ldap server (that the object is
/// new LdapModification(LdapModificationOp.Add, /// currently connected to) using the specified name, password, Ldap version,
/// "mail", "tesla@email.com") /// and constraints.
/// }); /// If the object has been disconnected from an Ldap server,
/// /// this method attempts to reconnect to the server. If the object
/// // disconnect from the LDAP server /// has already authenticated, the old authentication is discarded.
/// connection.Disconnect(); /// </summary>
/// /// <param name="dn">If non-null and non-empty, specifies that the
/// Terminal.Flush(); /// connection and all operations through it should
/// } /// be authenticated with dn as the distinguished
/// } /// name.</param>
/// </code> /// <param name="password">If non-null and non-empty, specifies that the
/// </example> /// connection and all operations through it should
public class LdapConnection : IDisposable /// be authenticated with dn as the distinguished
{ /// name and password.
private const int LdapV3 = 3; /// Note: the application should use care in the use
/// of String password objects. These are long lived
private readonly CancellationTokenSource _cts = new CancellationTokenSource(); /// objects, and may expose a security risk, especially
/// in objects that are serialized. The LdapConnection
private Connection _conn; /// keeps no long lived instances of these objects.</param>
private bool _isDisposing; /// <returns>
/// A <see cref="Task" /> representing the asynchronous operation.
/// <summary> /// </returns>
/// Returns the protocol version uses to authenticate. public Task Bind(String dn, String password) => this.Bind(LdapV3, dn, password);
/// 0 is returned if no authentication has been performed.
/// </summary> /// <summary>
/// <value> /// Synchronously authenticates to the Ldap server (that the object is
/// The protocol version. /// currently connected to) using the specified name, password, Ldap version,
/// </value> /// and constraints.
public int ProtocolVersion => BindProperties?.ProtocolVersion ?? LdapV3; /// If the object has been disconnected from an Ldap server,
/// this method attempts to reconnect to the server. If the object
/// <summary> /// has already authenticated, the old authentication is discarded.
/// Returns the distinguished name (DN) used for as the bind name during /// </summary>
/// the last successful bind operation. null is returned /// <param name="version">The Ldap protocol version, use Ldap_V3.
/// if no authentication has been performed or if the bind resulted in /// Ldap_V2 is not supported.</param>
/// an anonymous connection. /// <param name="dn">If non-null and non-empty, specifies that the
/// </summary> /// connection and all operations through it should
/// <value> /// be authenticated with dn as the distinguished
/// The authentication dn. /// name.</param>
/// </value> /// <param name="password">If non-null and non-empty, specifies that the
public string AuthenticationDn => BindProperties == null ? null : (BindProperties.Anonymous ? null : BindProperties.AuthenticationDN); /// connection and all operations through it should
/// be authenticated with dn as the distinguished
/// <summary> /// name and passwd as password.
/// Returns the method used to authenticate the connection. The return /// Note: the application should use care in the use
/// value is one of the following:. /// of String password objects. These are long lived
/// <ul><li>"none" indicates the connection is not authenticated.</li><li> /// objects, and may expose a security risk, especially
/// "simple" indicates simple authentication was used or that a null /// in objects that are serialized. The LdapConnection
/// or empty authentication DN was specified. /// keeps no long lived instances of these objects.</param>
/// </li><li>"sasl" indicates that a SASL mechanism was used to authenticate</li></ul> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// </summary> public Task Bind(Int32 version, String dn, String password) {
/// <value> dn = String.IsNullOrEmpty(dn) ? String.Empty : dn.Trim();
/// The authentication method. SByte[] passwordData = String.IsNullOrWhiteSpace(password) ? new SByte[] { } : Encoding.UTF8.GetSBytes(password);
/// </value>
public string AuthenticationMethod => BindProperties == null ? "simple" : BindProperties.AuthenticationMethod; Boolean anonymous = false;
/// <summary> if(passwordData.Length == 0) {
/// Indicates whether the connection represented by this object is open anonymous = true; // anonymous, password length zero with simple bind
/// at this time. dn = String.Empty; // set to null if anonymous
/// </summary> }
/// <returns>
/// True if connection is open; false if the connection is closed. this.BindProperties = new BindProperties(version, dn, "simple", anonymous);
/// </returns>
public bool Connected => _conn?.IsConnected == true; return this.RequestLdapMessage(new LdapBindRequest(version, dn, passwordData));
}
internal BindProperties BindProperties { get; set; }
/// <summary>
internal List<RfcLdapMessage> Messages { get; } = new List<RfcLdapMessage>(); /// Connects to the specified host and port.
/// If this LdapConnection object represents an open connection, the
/// <inheritdoc /> /// connection is closed first before the new connection is opened.
public void Dispose() /// At this point, there is no authentication, and any operations are
{ /// conducted as an anonymous client.
if (_isDisposing) return; /// </summary>
/// <param name="host">A host name or a dotted string representing the IP address
_isDisposing = true; /// of a host running an Ldap server.</param>
Disconnect(); /// <param name="port">The TCP or UDP port number to connect to or contact.
_cts?.Dispose(); /// The default Ldap port is 389.</param>
} /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task Connect(String host, Int32 port) {
/// <summary> TcpClient tcpClient = new TcpClient();
/// Synchronously authenticates to the Ldap server (that the object is await tcpClient.ConnectAsync(host, port).ConfigureAwait(false);
/// currently connected to) using the specified name, password, Ldap version, this._conn = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 0);
/// and constraints.
/// If the object has been disconnected from an Ldap server,
/// this method attempts to reconnect to the server. If the object
/// has already authenticated, the old authentication is discarded.
/// </summary>
/// <param name="dn">If non-null and non-empty, specifies that the
/// connection and all operations through it should
/// be authenticated with dn as the distinguished
/// name.</param>
/// <param name="password">If non-null and non-empty, specifies that the
/// connection and all operations through it should
/// be authenticated with dn as the distinguished
/// name and password.
/// Note: the application should use care in the use
/// of String password objects. These are long lived
/// objects, and may expose a security risk, especially
/// in objects that are serialized. The LdapConnection
/// keeps no long lived instances of these objects.</param>
/// <returns>
/// A <see cref="Task" /> representing the asynchronous operation.
/// </returns>
public Task Bind(string dn, string password) => Bind(LdapV3, dn, password);
/// <summary>
/// Synchronously authenticates to the Ldap server (that the object is
/// currently connected to) using the specified name, password, Ldap version,
/// and constraints.
/// If the object has been disconnected from an Ldap server,
/// this method attempts to reconnect to the server. If the object
/// has already authenticated, the old authentication is discarded.
/// </summary>
/// <param name="version">The Ldap protocol version, use Ldap_V3.
/// Ldap_V2 is not supported.</param>
/// <param name="dn">If non-null and non-empty, specifies that the
/// connection and all operations through it should
/// be authenticated with dn as the distinguished
/// name.</param>
/// <param name="password">If non-null and non-empty, specifies that the
/// connection and all operations through it should
/// be authenticated with dn as the distinguished
/// name and passwd as password.
/// Note: the application should use care in the use
/// of String password objects. These are long lived
/// objects, and may expose a security risk, especially
/// in objects that are serialized. The LdapConnection
/// keeps no long lived instances of these objects.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task Bind(int version, string dn, string password)
{
dn = string.IsNullOrEmpty(dn) ? string.Empty : dn.Trim();
var passwordData = string.IsNullOrWhiteSpace(password) ? new sbyte[] { } : Encoding.UTF8.GetSBytes(password);
var anonymous = false;
if (passwordData.Length == 0)
{
anonymous = true; // anonymous, password length zero with simple bind
dn = string.Empty; // set to null if anonymous
}
BindProperties = new BindProperties(version, dn, "simple", anonymous);
return RequestLdapMessage(new LdapBindRequest(version, dn, passwordData));
}
/// <summary>
/// Connects to the specified host and port.
/// If this LdapConnection object represents an open connection, the
/// connection is closed first before the new connection is opened.
/// At this point, there is no authentication, and any operations are
/// conducted as an anonymous client.
/// </summary>
/// <param name="host">A host name or a dotted string representing the IP address
/// of a host running an Ldap server.</param>
/// <param name="port">The TCP or UDP port number to connect to or contact.
/// The default Ldap port is 389.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task Connect(string host, int port)
{
var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(host, port).ConfigureAwait(false);
_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
} }
/// <summary> /// <summary>
/// Synchronously disconnects from the Ldap server. /// Synchronously disconnects from the Ldap server.
/// Before the object can perform Ldap operations again, it must /// Before the object can perform Ldap operations again, it must
/// reconnect to the server by calling connect. /// reconnect to the server by calling connect.
/// 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 this._cts.Cancel();
_cts.Cancel(); this._conn.Disconnect();
_conn.Disconnect(); }
}
/// <summary>
/// <summary> /// Synchronously reads the entry for the specified distinguished name (DN),
/// Synchronously reads the entry for the specified distinguished name (DN), /// using the specified constraints, and retrieves only the specified
/// using the specified constraints, and retrieves only the specified /// attributes from the entry.
/// attributes from the entry. /// </summary>
/// </summary> /// <param name="dn">The distinguished name of the entry to retrieve.</param>
/// <param name="dn">The distinguished name of the entry to retrieve.</param> /// <param name="attrs">The names of the attributes to retrieve.</param>
/// <param name="attrs">The names of the attributes to retrieve.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="ct">The cancellation token.</param> /// <returns>
/// <returns> /// 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);
{ LdapEntry ret = null;
var sr = await Search(dn, LdapScope.ScopeSub, null, attrs, false, ct);
LdapEntry ret = null; if(sr.HasMore()) {
ret = sr.Next();
if (sr.HasMore()) if(sr.HasMore()) {
{ throw new LdapException("Read response is ambiguous, multiple entries returned", LdapStatusCode.AmbiguousResponse);
ret = sr.Next(); }
if (sr.HasMore()) }
{
throw new LdapException("Read response is ambiguous, multiple entries returned", LdapStatusCode.AmbiguousResponse); return ret;
} }
}
/// <summary>
return ret; /// Performs the search specified by the parameters,
} /// also allowing specification of constraints for the search (such
/// as the maximum number of entries to find or the maximum time to
/// <summary> /// wait for search results).
/// Performs the search specified by the parameters, /// </summary>
/// also allowing specification of constraints for the search (such /// <param name="base">The base distinguished name to search from.</param>
/// as the maximum number of entries to find or the maximum time to /// <param name="scope">The scope of the entries to search.</param>
/// wait for search results). /// <param name="filter">The search filter specifying the search criteria.</param>
/// </summary> /// <param name="attrs">The names of attributes to retrieve.</param>
/// <param name="base">The base distinguished name to search from.</param> /// <param name="typesOnly">If true, returns the names but not the values of
/// <param name="scope">The scope of the entries to search.</param> /// the attributes found. If false, returns the
/// <param name="filter">The search filter specifying the search criteria.</param> /// names and values for attributes found.</param>
/// <param name="attrs">The names of attributes to retrieve.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="typesOnly">If true, returns the names but not the values of /// <returns>
/// the attributes found. If false, returns the /// A <see cref="Task" /> representing the asynchronous operation.
/// names and values for attributes found.</param> /// </returns>
/// <param name="ct">The cancellation token.</param> public async Task<LdapSearchResults> Search(
/// <returns> String @base,
/// A <see cref="Task" /> representing the asynchronous operation. LdapScope scope,
/// </returns> String filter = "objectClass=*",
public async Task<LdapSearchResults> Search( String[] attrs = null,
string @base, Boolean typesOnly = false,
LdapScope scope, CancellationToken ct = default) {
string filter = "objectClass=*", // TODO: Add Search options
string[] attrs = null, LdapSearchRequest msg = new LdapSearchRequest(@base, scope, filter, attrs, 0, 1000, 0, typesOnly, null);
bool typesOnly = false,
CancellationToken ct = default) await this.RequestLdapMessage(msg, ct).ConfigureAwait(false);
{
// TODO: Add Search options return new LdapSearchResults(this.Messages, msg.MessageId);
var msg = new LdapSearchRequest(@base, scope, filter, attrs, 0, 1000, 0, typesOnly, null); }
await RequestLdapMessage(msg, ct).ConfigureAwait(false); /// <summary>
/// Modifies the specified dn.
return new LdapSearchResults(Messages, msg.MessageId); /// </summary>
} /// <param name="distinguishedName">Name of the distinguished.</param>
/// <param name="mods">The mods.</param>
/// <summary> /// <param name="ct">The cancellation token.</param>
/// Modifies the specified dn. /// <returns>
/// </summary> /// A <see cref="Task" /> representing the asynchronous operation.
/// <param name="distinguishedName">Name of the distinguished.</param> /// </returns>
/// <param name="mods">The mods.</param> /// <exception cref="ArgumentNullException">distinguishedName.</exception>
/// <param name="ct">The cancellation token.</param> public Task Modify(String distinguishedName, LdapModification[] mods, CancellationToken ct = default) {
/// <returns> if(distinguishedName == null) {
/// A <see cref="Task" /> representing the asynchronous operation. throw new ArgumentNullException(nameof(distinguishedName));
/// </returns> }
/// <exception cref="ArgumentNullException">distinguishedName.</exception>
public Task Modify(string distinguishedName, LdapModification[] mods, CancellationToken ct = default) return this.RequestLdapMessage(new LdapModifyRequest(distinguishedName, mods, null), ct);
{ }
if (distinguishedName == null)
{ internal async Task RequestLdapMessage(LdapMessage msg, CancellationToken ct = default) {
throw new ArgumentNullException(nameof(distinguishedName)); using(MemoryStream stream = new MemoryStream()) {
} LberEncoder.Encode(msg.Asn1Object, stream);
await this._conn.WriteDataAsync(stream.ToArray(), true, ct).ConfigureAwait(false);
return RequestLdapMessage(new LdapModifyRequest(distinguishedName, mods, null), ct);
} try {
while(new List<RfcLdapMessage>(this.Messages).Any(x => x.MessageId == msg.MessageId) == false) {
internal async Task RequestLdapMessage(LdapMessage msg, CancellationToken ct = default) await Task.Delay(100, ct).ConfigureAwait(false);
{ }
using (var stream = new MemoryStream()) } catch(ArgumentException) {
{ // expected
LberEncoder.Encode(msg.Asn1Object, stream); }
await _conn.WriteDataAsync(stream.ToArray(), true, ct).ConfigureAwait(false);
RfcLdapMessage first = new List<RfcLdapMessage>(this.Messages).FirstOrDefault(x => x.MessageId == msg.MessageId);
try
{ if(first != null) {
while (new List<RfcLdapMessage>(Messages).Any(x => x.MessageId == msg.MessageId) == false) LdapResponse response = new LdapResponse(first);
await Task.Delay(100, ct).ConfigureAwait(false); response.ChkResultCode();
} }
catch (ArgumentException) }
{ }
// expected
} internal void RetrieveMessages() {
while(!this._cts.IsCancellationRequested) {
var first = new List<RfcLdapMessage>(Messages).FirstOrDefault(x => x.MessageId == msg.MessageId); try {
Asn1Identifier asn1Id = new Asn1Identifier(this._conn.ActiveStream);
if (first != null)
{ if(asn1Id.Tag != Asn1Sequence.Tag) {
var response = new LdapResponse(first); continue; // loop looking for an RfcLdapMessage identifier
response.ChkResultCode(); }
}
} // Turn the message into an RfcMessage class
} Asn1Length asn1Len = new Asn1Length(this._conn.ActiveStream);
internal void RetrieveMessages() this.Messages.Add(new RfcLdapMessage(this._conn.ActiveStream, asn1Len.Length));
{ } catch(IOException) {
while (!_cts.IsCancellationRequested) // ignore
{ }
try }
{
var asn1Id = new Asn1Identifier(_conn.ActiveStream); // ReSharper disable once FunctionNeverReturns
}
if (asn1Id.Tag != Asn1Sequence.Tag) }
{
continue; // loop looking for an RfcLdapMessage identifier
}
// Turn the message into an RfcMessage class
var asn1Len = new Asn1Length(_conn.ActiveStream);
Messages.Add(new RfcLdapMessage(_conn.ActiveStream, asn1Len.Length));
}
catch (IOException)
{
// ignore
}
}
// ReSharper disable once FunctionNeverReturns
}
}
} }

View File

@ -1,292 +1,275 @@
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>
/// Encapsulates optional additional parameters or constraints to be applied to
/// an Ldap operation.
/// When included with LdapConstraints or LdapSearchConstraints
/// on an LdapConnection or with a specific operation request, it is
/// sent to the server along with operation requests.
/// </summary>
public class LdapControl {
/// <summary> /// <summary>
/// Encapsulates optional additional parameters or constraints to be applied to /// Initializes a new instance of the <see cref="LdapControl"/> class.
/// an Ldap operation. /// Constructs a new LdapControl object using the specified values.
/// When included with LdapConstraints or LdapSearchConstraints
/// on an LdapConnection or with a specific operation request, it is
/// sent to the server along with operation requests.
/// </summary> /// </summary>
public class LdapControl /// <param name="oid">The OID of the control, as a dotted string.</param>
{ /// <param name="critical">True if the Ldap operation should be discarded if
/// <summary> /// the control is not supported. False if
/// Initializes a new instance of the <see cref="LdapControl"/> class. /// the operation can be processed without the control.</param>
/// Constructs a new LdapControl object using the specified values. /// <param name="values">The control-specific data.</param>
/// </summary> /// <exception cref="ArgumentException">An OID must be specified.</exception>
/// <param name="oid">The OID of the control, as a dotted string.</param> public LdapControl(String oid, Boolean critical, SByte[] values) {
/// <param name="critical">True if the Ldap operation should be discarded if if(oid == null) {
/// the control is not supported. False if throw new ArgumentException("An OID must be specified");
/// the operation can be processed without the control.</param> }
/// <param name="values">The control-specific data.</param>
/// <exception cref="ArgumentException">An OID must be specified.</exception> this.Asn1Object = new RfcControl(
public LdapControl(string oid, bool critical, sbyte[] values) oid,
{ new Asn1Boolean(critical),
if (oid == null) values == null ? null : new Asn1OctetString(values));
{ }
throw new ArgumentException("An OID must be specified");
}
Asn1Object = new RfcControl(
oid,
new Asn1Boolean(critical),
values == null ? null : new Asn1OctetString(values));
}
/// <summary>
/// Returns the identifier of the control.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public string Id => Asn1Object.ControlType.StringValue();
/// <summary>
/// Returns whether the control is critical for the operation.
/// </summary>
/// <value>
/// <c>true</c> if critical; otherwise, <c>false</c>.
/// </value>
public bool Critical => Asn1Object.Criticality.BooleanValue();
internal static RespControlVector RegisteredControls { get; } = new RespControlVector(5);
internal RfcControl Asn1Object { get; }
/// <summary>
/// Registers a class to be instantiated on receipt of a control with the
/// given OID.
/// Any previous registration for the OID is overridden. The
/// controlClass must be an extension of LdapControl.
/// </summary>
/// <param name="oid">The object identifier of the control.</param>
/// <param name="controlClass">A class which can instantiate an LdapControl.</param>
public static void Register(string oid, Type controlClass)
=> RegisteredControls.RegisterResponseControl(oid, controlClass);
/// <summary>
/// Returns the control-specific data of the object.
/// </summary>
/// <returns>
/// The control-specific data of the object as a byte array,
/// or null if the control has no data.
/// </returns>
public sbyte[] GetValue() => Asn1Object.ControlValue?.ByteValue();
internal void SetValue(sbyte[] controlValue)
{
Asn1Object.ControlValue = new Asn1OctetString(controlValue);
}
}
/// <summary> /// <summary>
/// Represents a simple bind request. /// Returns the identifier of the control.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" /> /// <value>
public class LdapBindRequest : LdapMessage /// The identifier.
{ /// </value>
/// <summary> public String Id => this.Asn1Object.ControlType.StringValue();
/// Initializes a new instance of the <see cref="LdapBindRequest"/> class.
/// Constructs a simple bind request.
/// </summary>
/// <param name="version">The Ldap protocol version, use Ldap_V3.
/// Ldap_V2 is not supported.</param>
/// <param name="dn">If non-null and non-empty, specifies that the
/// connection and all operations through it should
/// be authenticated with dn as the distinguished
/// name.</param>
/// <param name="password">If non-null and non-empty, specifies that the
/// connection and all operations through it should
/// be authenticated with dn as the distinguished
/// name and passwd as password.</param>
public LdapBindRequest(int version, string dn, sbyte[] password)
: base(LdapOperation.BindRequest, new RfcBindRequest(version, dn, password))
{
}
/// <summary>
/// Retrieves the Authentication DN for a bind request.
/// </summary>
/// <value>
/// The authentication dn.
/// </value>
public string AuthenticationDN => Asn1Object.RequestDn;
/// <inheritdoc />
public override string ToString() => Asn1Object.ToString();
}
/// <summary> /// <summary>
/// Encapsulates a continuation reference from an asynchronous search operation. /// Returns whether the control is critical for the operation.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" /> /// <value>
internal class LdapSearchResultReference : LdapMessage /// <c>true</c> if critical; otherwise, <c>false</c>.
{ /// </value>
/// <summary> public Boolean Critical => this.Asn1Object.Criticality.BooleanValue();
/// Initializes a new instance of the <see cref="LdapSearchResultReference"/> class.
/// Constructs an LdapSearchResultReference object. internal static RespControlVector RegisteredControls { get; } = new RespControlVector(5);
/// </summary>
/// <param name="message">The LdapMessage with a search reference.</param> internal RfcControl Asn1Object {
internal LdapSearchResultReference(RfcLdapMessage message) get;
: base(message) }
{
}
/// <summary>
/// Returns any URLs in the object.
/// </summary>
/// <value>
/// The referrals.
/// </value>
public string[] Referrals
{
get
{
var references = ((RfcSearchResultReference)Message.Response).ToArray();
var srefs = new string[references.Length];
for (var i = 0; i < references.Length; i++)
{
srefs[i] = ((Asn1OctetString)references[i]).StringValue();
}
return srefs;
}
}
}
internal class LdapResponse : LdapMessage
{
internal LdapResponse(RfcLdapMessage message)
: base(message)
{
}
public string ErrorMessage => ((IRfcResponse)Message.Response).GetErrorMessage().StringValue();
public string MatchedDN => ((IRfcResponse)Message.Response).GetMatchedDN().StringValue();
public LdapStatusCode ResultCode => Message.Response is RfcSearchResultEntry ||
(IRfcResponse)Message.Response is RfcIntermediateResponse
? LdapStatusCode.Success
: (LdapStatusCode)((IRfcResponse)Message.Response).GetResultCode().IntValue();
internal LdapException Exception { get; set; }
internal void ChkResultCode()
{
if (Exception != null)
{
throw Exception;
}
switch (ResultCode)
{
case LdapStatusCode.Success:
case LdapStatusCode.CompareTrue:
case LdapStatusCode.CompareFalse:
break;
case LdapStatusCode.Referral:
throw new LdapException(
"Automatic referral following not enabled",
LdapStatusCode.Referral,
ErrorMessage);
default:
throw new LdapException(ResultCode.ToString().Humanize(), ResultCode, ErrorMessage, MatchedDN);
}
}
}
/// <summary> /// <summary>
/// The RespControlVector class implements extends the /// Registers a class to be instantiated on receipt of a control with the
/// existing Vector class so that it can be used to maintain a /// given OID.
/// list of currently registered control responses. /// Any previous registration for the OID is overridden. The
/// controlClass must be an extension of LdapControl.
/// </summary> /// </summary>
internal class RespControlVector : List<RespControlVector.RegisteredControl> /// <param name="oid">The object identifier of the control.</param>
{ /// <param name="controlClass">A class which can instantiate an LdapControl.</param>
private readonly object _syncLock = new object(); public static void Register(String oid, Type controlClass)
=> RegisteredControls.RegisterResponseControl(oid, controlClass);
public RespControlVector(int cap)
: base(cap)
{
}
public void RegisterResponseControl(string oid, Type controlClass)
{
lock (_syncLock)
{
Add(new RegisteredControl(this, oid, controlClass));
}
}
/// <summary>
/// Inner class defined to create a temporary object to encapsulate
/// all registration information about a response control.
/// </summary>
internal class RegisteredControl
{
public RegisteredControl(RespControlVector enclosingInstance, string oid, Type controlClass)
{
EnclosingInstance = enclosingInstance;
MyOid = oid;
MyClass = controlClass;
}
internal Type MyClass { get; }
internal string MyOid { get; }
private RespControlVector EnclosingInstance { get; }
}
}
/// <summary> /// <summary>
/// Represents and Ldap Bind Request. /// Returns the control-specific data of the object.
/// <pre> /// </summary>
/// BindRequest ::= [APPLICATION 0] SEQUENCE { /// <returns>
/// version INTEGER (1 .. 127), /// The control-specific data of the object as a byte array,
/// name LdapDN, /// or null if the control has no data.
/// authentication AuthenticationChoice } /// </returns>
/// </pre></summary> public SByte[] GetValue() => this.Asn1Object.ControlValue?.ByteValue();
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="IRfcRequest" /> internal void SetValue(SByte[] controlValue) => this.Asn1Object.ControlValue = new Asn1OctetString(controlValue);
internal sealed class RfcBindRequest }
: Asn1Sequence, IRfcRequest
{ /// <summary>
private readonly sbyte[] _password; /// Represents a simple bind request.
private static readonly Asn1Identifier Id = new Asn1Identifier(LdapOperation.BindRequest); /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
public RfcBindRequest(int version, string name, sbyte[] password) public class LdapBindRequest : LdapMessage {
: base(3) /// <summary>
{ /// Initializes a new instance of the <see cref="LdapBindRequest"/> class.
_password = password; /// Constructs a simple bind request.
Add(new Asn1Integer(version)); /// </summary>
Add(name); /// <param name="version">The Ldap protocol version, use Ldap_V3.
Add(new RfcAuthenticationChoice(password)); /// Ldap_V2 is not supported.</param>
} /// <param name="dn">If non-null and non-empty, specifies that the
/// connection and all operations through it should
public Asn1Integer Version /// be authenticated with dn as the distinguished
{ /// name.</param>
get => (Asn1Integer)Get(0); /// <param name="password">If non-null and non-empty, specifies that the
set => Set(0, value); /// connection and all operations through it should
} /// be authenticated with dn as the distinguished
/// name and passwd as password.</param>
public Asn1OctetString Name public LdapBindRequest(Int32 version, String dn, SByte[] password)
{ : base(LdapOperation.BindRequest, new RfcBindRequest(version, dn, password)) {
get => (Asn1OctetString)Get(1); }
set => Set(1, value);
} /// <summary>
/// Retrieves the Authentication DN for a bind request.
public RfcAuthenticationChoice AuthenticationChoice /// </summary>
{ /// <value>
get => (RfcAuthenticationChoice)Get(2); /// The authentication dn.
set => Set(2, value); /// </value>
} public String AuthenticationDN => this.Asn1Object.RequestDn;
public override Asn1Identifier GetIdentifier() => Id; /// <inheritdoc />
public override String ToString() => this.Asn1Object.ToString();
public string GetRequestDN() => ((Asn1OctetString)Get(1)).StringValue(); }
}
/// <summary>
/// Encapsulates a continuation reference from an asynchronous search operation.
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
internal class LdapSearchResultReference : LdapMessage {
/// <summary>
/// Initializes a new instance of the <see cref="LdapSearchResultReference"/> class.
/// Constructs an LdapSearchResultReference object.
/// </summary>
/// <param name="message">The LdapMessage with a search reference.</param>
internal LdapSearchResultReference(RfcLdapMessage message)
: base(message) {
}
/// <summary>
/// Returns any URLs in the object.
/// </summary>
/// <value>
/// The referrals.
/// </value>
public String[] Referrals {
get {
Asn1Object[] references = ((RfcSearchResultReference)this.Message.Response).ToArray();
String[] srefs = new String[references.Length];
for(Int32 i = 0; i < references.Length; i++) {
srefs[i] = ((Asn1OctetString)references[i]).StringValue();
}
return srefs;
}
}
}
internal class LdapResponse : LdapMessage {
internal LdapResponse(RfcLdapMessage message)
: base(message) {
}
public String ErrorMessage => ((IRfcResponse)this.Message.Response).GetErrorMessage().StringValue();
public String MatchedDN => ((IRfcResponse)this.Message.Response).GetMatchedDN().StringValue();
public LdapStatusCode ResultCode => this.Message.Response is RfcSearchResultEntry ||
(IRfcResponse)this.Message.Response is RfcIntermediateResponse
? LdapStatusCode.Success
: (LdapStatusCode)((IRfcResponse)this.Message.Response).GetResultCode().IntValue();
internal LdapException Exception {
get; set;
}
internal void ChkResultCode() {
if(this.Exception != null) {
throw this.Exception;
}
switch(this.ResultCode) {
case LdapStatusCode.Success:
case LdapStatusCode.CompareTrue:
case LdapStatusCode.CompareFalse:
break;
case LdapStatusCode.Referral:
throw new LdapException(
"Automatic referral following not enabled",
LdapStatusCode.Referral,
this.ErrorMessage);
default:
throw new LdapException(this.ResultCode.ToString().Humanize(), this.ResultCode, this.ErrorMessage, this.MatchedDN);
}
}
}
/// <summary>
/// The RespControlVector class implements extends the
/// existing Vector class so that it can be used to maintain a
/// list of currently registered control responses.
/// </summary>
internal class RespControlVector : List<RespControlVector.RegisteredControl> {
private readonly Object _syncLock = new Object();
public RespControlVector(Int32 cap)
: base(cap) {
}
public void RegisterResponseControl(String oid, Type controlClass) {
lock(this._syncLock) {
this.Add(new RegisteredControl(this, oid, controlClass));
}
}
/// <summary>
/// Inner class defined to create a temporary object to encapsulate
/// all registration information about a response control.
/// </summary>
internal class RegisteredControl {
public RegisteredControl(RespControlVector enclosingInstance, String oid, Type controlClass) {
this.EnclosingInstance = enclosingInstance;
this.MyOid = oid;
this.MyClass = controlClass;
}
internal Type MyClass {
get;
}
internal String MyOid {
get;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
private RespControlVector EnclosingInstance {
get;
}
}
}
/// <summary>
/// Represents and Ldap Bind Request.
/// <pre>
/// BindRequest ::= [APPLICATION 0] SEQUENCE {
/// version INTEGER (1 .. 127),
/// name LdapDN,
/// authentication AuthenticationChoice }
/// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="IRfcRequest" />
internal sealed class RfcBindRequest
: Asn1Sequence, IRfcRequest {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
private readonly SByte[] _password;
private static readonly Asn1Identifier Id = new Asn1Identifier(LdapOperation.BindRequest);
public RfcBindRequest(Int32 version, String name, SByte[] password)
: base(3) {
this._password = password;
this.Add(new Asn1Integer(version));
this.Add(name);
this.Add(new RfcAuthenticationChoice(password));
}
public Asn1Integer Version {
get => (Asn1Integer)this.Get(0);
set => this.Set(0, value);
}
public Asn1OctetString Name {
get => (Asn1OctetString)this.Get(1);
set => this.Set(1, value);
}
public RfcAuthenticationChoice AuthenticationChoice {
get => (RfcAuthenticationChoice)this.Get(2);
set => this.Set(2, value);
}
public override Asn1Identifier GetIdentifier() => Id;
public String GetRequestDN() => ((Asn1OctetString)this.Get(1)).StringValue();
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,135 +1,130 @@
namespace Unosquare.Swan.Networking.Ldap namespace Unosquare.Swan.Networking.Ldap {
{ /// <summary>
/// Ldap Modification Operators.
/// </summary>
public enum LdapModificationOp {
/// <summary> /// <summary>
/// Ldap Modification Operators. /// Adds the listed values to the given attribute, creating
/// the attribute if it does not already exist.
/// </summary> /// </summary>
public enum LdapModificationOp Add = 0,
{
/// <summary>
/// Adds the listed values to the given attribute, creating
/// the attribute if it does not already exist.
/// </summary>
Add = 0,
/// <summary>
/// Deletes the listed values from the given attribute,
/// removing the entire attribute (1) if no values are listed or
/// (2) if all current values of the attribute are listed for
/// deletion.
/// </summary>
Delete = 1,
/// <summary>
/// Replaces all existing values of the given attribute
/// with the new values listed, creating the attribute if it
/// does not already exist.
/// A replace with no value deletes the entire attribute if it
/// exists, and is ignored if the attribute does not exist.
/// </summary>
Replace = 2,
}
/// <summary> /// <summary>
/// LDAP valid scopes. /// Deletes the listed values from the given attribute,
/// removing the entire attribute (1) if no values are listed or
/// (2) if all current values of the attribute are listed for
/// deletion.
/// </summary> /// </summary>
public enum LdapScope Delete = 1,
{
/// <summary>
/// Used with search to specify that the scope of entrys to search is to
/// search only the base object.
/// </summary>
ScopeBase = 0,
/// <summary>
/// Used with search to specify that the scope of entrys to search is to
/// search only the immediate subordinates of the base object.
/// </summary>
ScopeOne = 1,
/// <summary>
/// Used with search to specify that the scope of entrys to search is to
/// search the base object and all entries within its subtree.
/// </summary>
ScopeSub = 2,
}
/// <summary> /// <summary>
/// Substring Operators. /// Replaces all existing values of the given attribute
/// with the new values listed, creating the attribute if it
/// does not already exist.
/// A replace with no value deletes the entire attribute if it
/// exists, and is ignored if the attribute does not exist.
/// </summary> /// </summary>
internal enum SubstringOp Replace = 2,
{ }
/// <summary>
/// Search Filter Identifier for an INITIAL component of a SUBSTRING. /// <summary>
/// Note: An initial SUBSTRING is represented as "value*". /// LDAP valid scopes.
/// </summary> /// </summary>
Initial = 0, public enum LdapScope {
/// <summary>
/// Search Filter Identifier for an ANY component of a SUBSTRING.
/// Note: An ANY SUBSTRING is represented as "*value*".
/// </summary>
Any = 1,
/// <summary>
/// Search Filter Identifier for a FINAL component of a SUBSTRING.
/// Note: A FINAL SUBSTRING is represented as "*value".
/// </summary>
Final = 2,
}
/// <summary> /// <summary>
/// Filtering Operators. /// Used with search to specify that the scope of entrys to search is to
/// search only the base object.
/// </summary> /// </summary>
internal enum FilterOp ScopeBase = 0,
{
/// <summary> /// <summary>
/// Identifier for AND component. /// Used with search to specify that the scope of entrys to search is to
/// </summary> /// search only the immediate subordinates of the base object.
And = 0, /// </summary>
ScopeOne = 1,
/// <summary>
/// Identifier for OR component. /// <summary>
/// </summary> /// Used with search to specify that the scope of entrys to search is to
Or = 1, /// search the base object and all entries within its subtree.
/// </summary>
/// <summary> ScopeSub = 2,
/// Identifier for NOT component. }
/// </summary>
Not = 2, /// <summary>
/// Substring Operators.
/// <summary> /// </summary>
/// Identifier for EQUALITY_MATCH component. internal enum SubstringOp {
/// </summary> /// <summary>
EqualityMatch = 3, /// Search Filter Identifier for an INITIAL component of a SUBSTRING.
/// Note: An initial SUBSTRING is represented as "value*".
/// <summary> /// </summary>
/// Identifier for SUBSTRINGS component. Initial = 0,
/// </summary>
Substrings = 4, /// <summary>
/// Search Filter Identifier for an ANY component of a SUBSTRING.
/// <summary> /// Note: An ANY SUBSTRING is represented as "*value*".
/// Identifier for GREATER_OR_EQUAL component. /// </summary>
/// </summary> Any = 1,
GreaterOrEqual = 5,
/// <summary>
/// <summary> /// Search Filter Identifier for a FINAL component of a SUBSTRING.
/// Identifier for LESS_OR_EQUAL component. /// Note: A FINAL SUBSTRING is represented as "*value".
/// </summary> /// </summary>
LessOrEqual = 6, Final = 2,
}
/// <summary>
/// Identifier for PRESENT component. /// <summary>
/// </summary> /// Filtering Operators.
Present = 7, /// </summary>
internal enum FilterOp {
/// <summary> /// <summary>
/// Identifier for APPROX_MATCH component. /// Identifier for AND component.
/// </summary> /// </summary>
ApproxMatch = 8, And = 0,
/// <summary> /// <summary>
/// Identifier for EXTENSIBLE_MATCH component. /// Identifier for OR component.
/// </summary> /// </summary>
ExtensibleMatch = 9, Or = 1,
}
/// <summary>
/// Identifier for NOT component.
/// </summary>
Not = 2,
/// <summary>
/// Identifier for EQUALITY_MATCH component.
/// </summary>
EqualityMatch = 3,
/// <summary>
/// Identifier for SUBSTRINGS component.
/// </summary>
Substrings = 4,
/// <summary>
/// Identifier for GREATER_OR_EQUAL component.
/// </summary>
GreaterOrEqual = 5,
/// <summary>
/// Identifier for LESS_OR_EQUAL component.
/// </summary>
LessOrEqual = 6,
/// <summary>
/// Identifier for PRESENT component.
/// </summary>
Present = 7,
/// <summary>
/// Identifier for APPROX_MATCH component.
/// </summary>
ApproxMatch = 8,
/// <summary>
/// Identifier for EXTENSIBLE_MATCH component.
/// </summary>
ExtensibleMatch = 9,
}
} }

View File

@ -1,140 +1,120 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// The base class for Ldap request and response messages.
/// Subclassed by response messages used in asynchronous operations.
/// </summary>
public class LdapMessage {
internal RfcLdapMessage Message;
private Int32 _imsgNum = -1; // This instance LdapMessage number
private LdapOperation _messageType = LdapOperation.Unknown;
private String _stringTag;
internal LdapMessage() {
}
/// <summary> /// <summary>
/// The base class for Ldap request and response messages. /// Initializes a new instance of the <see cref="LdapMessage"/> class.
/// Subclassed by response messages used in asynchronous operations. /// Creates an LdapMessage when sending a protocol operation and sends
/// some optional controls with the message.
/// </summary> /// </summary>
public class LdapMessage /// <param name="type">The type.</param>
{ /// <param name="op">The operation type of message.</param>
internal RfcLdapMessage Message; /// <param name="controls">The controls to use with the operation.</param>
/// <seealso cref="Type"></seealso>
private int _imsgNum = -1; // This instance LdapMessage number internal LdapMessage(LdapOperation type, IRfcRequest op, LdapControl[] controls = null) {
// Get a unique number for this request message
private LdapOperation _messageType = LdapOperation.Unknown; this._messageType = type;
RfcControls asn1Ctrls = null;
private string _stringTag;
if(controls != null) {
internal LdapMessage() // Move LdapControls into an RFC 2251 Controls object.
{ asn1Ctrls = new RfcControls();
}
foreach(LdapControl t in controls) {
/// <summary> asn1Ctrls.Add(t.Asn1Object);
/// Initializes a new instance of the <see cref="LdapMessage"/> class. }
/// Creates an LdapMessage when sending a protocol operation and sends }
/// some optional controls with the message.
/// </summary> // create RFC 2251 LdapMessage
/// <param name="type">The type.</param> this.Message = new RfcLdapMessage(op, asn1Ctrls);
/// <param name="op">The operation type of message.</param> }
/// <param name="controls">The controls to use with the operation.</param>
/// <seealso cref="Type"></seealso> /// <summary>
internal LdapMessage(LdapOperation type, IRfcRequest op, LdapControl[] controls = null) /// Initializes a new instance of the <see cref="LdapMessage"/> class.
{ /// Creates an Rfc 2251 LdapMessage when the libraries receive a response
// Get a unique number for this request message /// from a command.
_messageType = type; /// </summary>
RfcControls asn1Ctrls = null; /// <param name="message">A response message.</param>
internal LdapMessage(RfcLdapMessage message) => this.Message = message;
if (controls != null)
{ /// <summary>
// Move LdapControls into an RFC 2251 Controls object. /// Returns the message ID. The message ID is an integer value
asn1Ctrls = new RfcControls(); /// identifying the Ldap request and its response.
/// </summary>
foreach (var t in controls) /// <value>
{ /// The message identifier.
asn1Ctrls.Add(t.Asn1Object); /// </value>
} public virtual Int32 MessageId {
} get {
if(this._imsgNum == -1) {
// create RFC 2251 LdapMessage this._imsgNum = this.Message.MessageId;
Message = new RfcLdapMessage(op, asn1Ctrls); }
}
return this._imsgNum;
/// <summary> }
/// Initializes a new instance of the <see cref="LdapMessage"/> class. }
/// Creates an Rfc 2251 LdapMessage when the libraries receive a response
/// from a command. /// <summary>
/// </summary> /// Indicates whether the message is a request or a response.
/// <param name="message">A response message.</param> /// </summary>
internal LdapMessage(RfcLdapMessage message) => Message = message; /// <value>
/// <c>true</c> if request; otherwise, <c>false</c>.
/// <summary> /// </value>
/// Returns the message ID. The message ID is an integer value public virtual Boolean Request => this.Message.IsRequest();
/// identifying the Ldap request and its response.
/// </summary> internal LdapOperation Type {
/// <value> get {
/// The message identifier. if(this._messageType == LdapOperation.Unknown) {
/// </value> this._messageType = this.Message.Type;
public virtual int MessageId }
{
get return this._messageType;
{ }
if (_imsgNum == -1) }
{
_imsgNum = Message.MessageId; internal virtual RfcLdapMessage Asn1Object => this.Message;
}
internal virtual LdapMessage RequestingMessage => this.Message.RequestingMessage;
return _imsgNum;
} /// <summary>
} /// Retrieves the identifier tag for this message.
/// An identifier can be associated with a message with the
/// <summary> /// <c>setTag</c> method.
/// Indicates whether the message is a request or a response. /// Tags are set by the application and not by the API or the server.
/// </summary> /// If a server response <c>isRequest() == false</c> has no tag,
/// <value> /// the tag associated with the corresponding server request is used.
/// <c>true</c> if request; otherwise, <c>false</c>. /// </summary>
/// </value> /// <value>
public virtual bool Request => Message.IsRequest(); /// The tag.
/// </value>
internal LdapOperation Type public virtual String Tag {
{ get => this._stringTag ?? (this.Request ? null : this.RequestingMessage?._stringTag);
get
{ set => this._stringTag = value;
if (_messageType == LdapOperation.Unknown) }
{
_messageType = Message.Type; private String Name => this.Type.ToString();
}
/// <summary>
return _messageType; /// Returns a <see cref="System.String" /> that represents this instance.
} /// </summary>
} /// <returns>
/// A <see cref="System.String" /> that represents this instance.
internal virtual RfcLdapMessage Asn1Object => Message; /// </returns>
public override String ToString() => $"{this.Name}({this.MessageId}): {this.Message}";
internal virtual LdapMessage RequestingMessage => Message.RequestingMessage; }
/// <summary>
/// Retrieves the identifier tag for this message.
/// An identifier can be associated with a message with the
/// <c>setTag</c> method.
/// Tags are set by the application and not by the API or the server.
/// If a server response <c>isRequest() == false</c> has no tag,
/// the tag associated with the corresponding server request is used.
/// </summary>
/// <value>
/// The tag.
/// </value>
public virtual string Tag
{
get
{
if (_stringTag != null)
{
return _stringTag;
}
return Request ? null : RequestingMessage?._stringTag;
}
set => _stringTag = value;
}
private string Name => Type.ToString();
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString() => $"{Name}({MessageId}): {Message}";
}
} }

View File

@ -1,78 +1,79 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// A single add, delete, or replace operation to an LdapAttribute.
/// An LdapModification contains information on the type of modification
/// being performed, the name of the attribute to be replaced, and the new
/// value. Multiple modifications are expressed as an array of modifications,
/// i.e., <c>LdapModification[]</c>.
/// An LdapModification or an LdapModification array enable you to modify
/// an attribute of an Ldap entry. The entire array of modifications must
/// be performed by the server as a single atomic operation in the order they
/// are listed. No changes are made to the directory unless all the operations
/// succeed. If all succeed, a success result is returned to the application.
/// It should be noted that if the connection fails during a modification,
/// it is indeterminate whether the modification occurred or not.
/// There are three types of modification operations: Add, Delete,
/// and Replace.
/// <b>Add: </b>Creates the attribute if it doesn't exist, and adds
/// the specified values. This operation must contain at least one value, and
/// all values of the attribute must be unique.
/// <b>Delete: </b>Deletes specified values from the attribute. If no
/// values are specified, or if all existing values of the attribute are
/// specified, the attribute is removed. Mandatory attributes cannot be
/// removed.
/// <b>Replace: </b>Creates the attribute if necessary, and replaces
/// all existing values of the attribute with the specified values.
/// If you wish to keep any existing values of a multi-valued attribute,
/// you must include these values in the replace operation.
/// A replace operation with no value will remove the entire attribute if it
/// exists, and is ignored if the attribute does not exist.
/// Additional information on Ldap modifications is available in section 4.6
/// of. <a href="http://www.ietf.org/rfc/rfc2251.txt">rfc2251.txt</a>
/// </summary>
/// <seealso cref="LdapConnection.Modify"></seealso>
/// <seealso cref="LdapAttribute"></seealso>
public sealed class LdapModification : LdapMessage {
/// <summary> /// <summary>
/// A single add, delete, or replace operation to an LdapAttribute. /// Initializes a new instance of the <see cref="LdapModification" /> class.
/// An LdapModification contains information on the type of modification /// Specifies a modification to be made to an attribute.
/// being performed, the name of the attribute to be replaced, and the new
/// value. Multiple modifications are expressed as an array of modifications,
/// i.e., <c>LdapModification[]</c>.
/// An LdapModification or an LdapModification array enable you to modify
/// an attribute of an Ldap entry. The entire array of modifications must
/// be performed by the server as a single atomic operation in the order they
/// are listed. No changes are made to the directory unless all the operations
/// succeed. If all succeed, a success result is returned to the application.
/// It should be noted that if the connection fails during a modification,
/// it is indeterminate whether the modification occurred or not.
/// There are three types of modification operations: Add, Delete,
/// and Replace.
/// <b>Add: </b>Creates the attribute if it doesn't exist, and adds
/// the specified values. This operation must contain at least one value, and
/// all values of the attribute must be unique.
/// <b>Delete: </b>Deletes specified values from the attribute. If no
/// values are specified, or if all existing values of the attribute are
/// specified, the attribute is removed. Mandatory attributes cannot be
/// removed.
/// <b>Replace: </b>Creates the attribute if necessary, and replaces
/// all existing values of the attribute with the specified values.
/// If you wish to keep any existing values of a multi-valued attribute,
/// you must include these values in the replace operation.
/// A replace operation with no value will remove the entire attribute if it
/// exists, and is ignored if the attribute does not exist.
/// Additional information on Ldap modifications is available in section 4.6
/// of. <a href="http://www.ietf.org/rfc/rfc2251.txt">rfc2251.txt</a>
/// </summary> /// </summary>
/// <seealso cref="LdapConnection.Modify"></seealso> /// <param name="op">The op.</param>
/// <seealso cref="LdapAttribute"></seealso> /// <param name="attr">The attribute to modify.</param>
public sealed class LdapModification : LdapMessage public LdapModification(LdapModificationOp op, LdapAttribute attr) {
{ this.Op = op;
/// <summary> this.Attribute = attr;
/// Initializes a new instance of the <see cref="LdapModification" /> class. }
/// Specifies a modification to be made to an attribute.
/// </summary> /// <summary>
/// <param name="op">The op.</param> /// Initializes a new instance of the <see cref="LdapModification"/> class.
/// <param name="attr">The attribute to modify.</param> /// </summary>
public LdapModification(LdapModificationOp op, LdapAttribute attr) /// <param name="op">The op.</param>
{ /// <param name="attrName">Name of the attribute.</param>
Op = op; /// <param name="attrValue">The attribute value.</param>
Attribute = attr; public LdapModification(LdapModificationOp op, String attrName, String attrValue)
} : this(op, new LdapAttribute(attrName, attrValue)) {
// placeholder
/// <summary> }
/// Initializes a new instance of the <see cref="LdapModification"/> class.
/// </summary> /// <summary>
/// <param name="op">The op.</param> /// Returns the attribute to modify, with any existing values.
/// <param name="attrName">Name of the attribute.</param> /// </summary>
/// <param name="attrValue">The attribute value.</param> /// <value>
public LdapModification(LdapModificationOp op, string attrName, string attrValue) /// The attribute.
: this(op, new LdapAttribute(attrName, attrValue)) /// </value>
{ public LdapAttribute Attribute {
// placeholder get;
} }
/// <summary> /// <summary>
/// Returns the attribute to modify, with any existing values. /// Returns the type of modification specified by this object.
/// </summary> /// </summary>
/// <value> /// <value>
/// The attribute. /// The op.
/// </value> /// </value>
public LdapAttribute Attribute { get; } public LdapModificationOp Op {
get;
/// <summary> }
/// Returns the type of modification specified by this object. }
/// </summary>
/// <value>
/// The op.
/// </value>
public LdapModificationOp Op { get; }
}
} }

View File

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

View File

@ -1,117 +1,114 @@
namespace Unosquare.Swan.Networking.Ldap namespace Unosquare.Swan.Networking.Ldap {
{ /// <summary>
/// LDAP Operation.
/// </summary>
internal enum LdapOperation {
/// <summary> /// <summary>
/// LDAP Operation. /// The unknown
/// </summary> /// </summary>
internal enum LdapOperation Unknown = -1,
{
/// <summary>
/// The unknown
/// </summary>
Unknown = -1,
/// <summary>
/// A bind request operation.
/// BIND_REQUEST = 0
/// </summary>
BindRequest = 0,
/// <summary>
/// A bind response operation.
/// BIND_RESPONSE = 1
/// </summary>
BindResponse = 1,
/// <summary>
/// An unbind request operation.
/// UNBIND_REQUEST = 2
/// </summary>
UnbindRequest = 2,
/// <summary>
/// A search request operation.
/// SEARCH_REQUEST = 3
/// </summary>
SearchRequest = 3,
/// <summary>
/// A search response containing data.
/// SEARCH_RESPONSE = 4
/// </summary>
SearchResponse = 4,
/// <summary>
/// A search result message - contains search status.
/// SEARCH_RESULT = 5
/// </summary>
SearchResult = 5,
/// <summary>
/// A modify request operation.
/// MODIFY_REQUEST = 6
/// </summary>
ModifyRequest = 6,
/// <summary>
/// A modify response operation.
/// MODIFY_RESPONSE = 7
/// </summary>
ModifyResponse = 7,
/// <summary>
/// An abandon request operation.
/// ABANDON_REQUEST = 16
/// </summary>
AbandonRequest = 16,
/// <summary>
/// A search result reference operation.
/// SEARCH_RESULT_REFERENCE = 19
/// </summary>
SearchResultReference = 19,
/// <summary>
/// An extended request operation.
/// EXTENDED_REQUEST = 23
/// </summary>
ExtendedRequest = 23,
/// <summary>
/// An extended response operation.
/// EXTENDED_RESPONSE = 24
/// </summary>
ExtendedResponse = 24,
/// <summary>
/// An intermediate response operation.
/// INTERMEDIATE_RESPONSE = 25
/// </summary>
IntermediateResponse = 25,
}
/// <summary> /// <summary>
/// ASN1 tags. /// A bind request operation.
/// BIND_REQUEST = 0
/// </summary> /// </summary>
internal enum Asn1IdentifierTag BindRequest = 0,
{
/// <summary> /// <summary>
/// Universal tag class. /// A bind response operation.
/// </summary> /// BIND_RESPONSE = 1
Universal = 0, /// </summary>
BindResponse = 1,
/// <summary>
/// Application-wide tag class. /// <summary>
/// </summary> /// An unbind request operation.
Application = 1, /// UNBIND_REQUEST = 2
/// </summary>
/// <summary> UnbindRequest = 2,
/// Context-specific tag class.
/// </summary> /// <summary>
Context = 2, /// A search request operation.
/// SEARCH_REQUEST = 3
/// <summary> /// </summary>
/// Private-use tag class. SearchRequest = 3,
/// </summary>
Private = 3, /// <summary>
} /// A search response containing data.
/// SEARCH_RESPONSE = 4
/// </summary>
SearchResponse = 4,
/// <summary>
/// A search result message - contains search status.
/// SEARCH_RESULT = 5
/// </summary>
SearchResult = 5,
/// <summary>
/// A modify request operation.
/// MODIFY_REQUEST = 6
/// </summary>
ModifyRequest = 6,
/// <summary>
/// A modify response operation.
/// MODIFY_RESPONSE = 7
/// </summary>
ModifyResponse = 7,
/// <summary>
/// An abandon request operation.
/// ABANDON_REQUEST = 16
/// </summary>
AbandonRequest = 16,
/// <summary>
/// A search result reference operation.
/// SEARCH_RESULT_REFERENCE = 19
/// </summary>
SearchResultReference = 19,
/// <summary>
/// An extended request operation.
/// EXTENDED_REQUEST = 23
/// </summary>
ExtendedRequest = 23,
/// <summary>
/// An extended response operation.
/// EXTENDED_RESPONSE = 24
/// </summary>
ExtendedResponse = 24,
/// <summary>
/// An intermediate response operation.
/// INTERMEDIATE_RESPONSE = 25
/// </summary>
IntermediateResponse = 25,
}
/// <summary>
/// ASN1 tags.
/// </summary>
internal enum Asn1IdentifierTag {
/// <summary>
/// Universal tag class.
/// </summary>
Universal = 0,
/// <summary>
/// Application-wide tag class.
/// </summary>
Application = 1,
/// <summary>
/// Context-specific tag class.
/// </summary>
Context = 2,
/// <summary>
/// Private-use tag class.
/// </summary>
Private = 3,
}
} }

View File

@ -1,185 +1,180 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.Collections;
using System.Collections;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// Represents an Ldap Search request.
/// </summary>
/// <seealso cref="LdapMessage" />
internal sealed class LdapSearchRequest : LdapMessage {
/// <summary> /// <summary>
/// Represents an Ldap Search request. /// Initializes a new instance of the <see cref="LdapSearchRequest"/> class.
/// </summary> /// </summary>
/// <seealso cref="LdapMessage" /> /// <param name="ldapBase">The base distinguished name to search from.</param>
internal sealed class LdapSearchRequest : LdapMessage /// <param name="scope">The scope of the entries to search. The following
{ /// are the valid options:.
/// <summary> /// <ul><li>SCOPE_BASE - searches only the base DN</li><li>SCOPE_ONE - searches only entries under the base DN</li><li>
/// Initializes a new instance of the <see cref="LdapSearchRequest"/> class. /// SCOPE_SUB - searches the base DN and all entries
/// </summary> /// within its subtree
/// <param name="ldapBase">The base distinguished name to search from.</param> /// </li></ul></param>
/// <param name="scope">The scope of the entries to search. The following /// <param name="filter">The search filter specifying the search criteria.</param>
/// are the valid options:. /// <param name="attrs">The names of attributes to retrieve.
/// <ul><li>SCOPE_BASE - searches only the base DN</li><li>SCOPE_ONE - searches only entries under the base DN</li><li> /// operation exceeds the time limit.</param>
/// SCOPE_SUB - searches the base DN and all entries /// <param name="dereference">Specifies when aliases should be dereferenced.
/// within its subtree /// Must be one of the constants defined in
/// </li></ul></param> /// LdapConstraints, which are DEREF_NEVER,
/// <param name="filter">The search filter specifying the search criteria.</param> /// DEREF_FINDING, DEREF_SEARCHING, or DEREF_ALWAYS.</param>
/// <param name="attrs">The names of attributes to retrieve. /// <param name="maxResults">The maximum number of search results to return
/// operation exceeds the time limit.</param> /// for a search request.
/// <param name="dereference">Specifies when aliases should be dereferenced. /// The search operation will be terminated by the server
/// Must be one of the constants defined in /// with an LdapException.SIZE_LIMIT_EXCEEDED if the
/// LdapConstraints, which are DEREF_NEVER, /// number of results exceed the maximum.</param>
/// DEREF_FINDING, DEREF_SEARCHING, or DEREF_ALWAYS.</param> /// <param name="serverTimeLimit">The maximum time in seconds that the server
/// <param name="maxResults">The maximum number of search results to return /// should spend returning search results. This is a
/// for a search request. /// server-enforced limit. A value of 0 means
/// The search operation will be terminated by the server /// no time limit.</param>
/// with an LdapException.SIZE_LIMIT_EXCEEDED if the /// <param name="typesOnly">If true, returns the names but not the values of
/// number of results exceed the maximum.</param> /// the attributes found. If false, returns the
/// <param name="serverTimeLimit">The maximum time in seconds that the server /// names and values for attributes found.</param>
/// should spend returning search results. This is a /// <param name="cont">Any controls that apply to the search request.
/// server-enforced limit. A value of 0 means /// or null if none.</param>
/// no time limit.</param> /// <seealso cref="LdapConnection.Search"></seealso>
/// <param name="typesOnly">If true, returns the names but not the values of public LdapSearchRequest(
/// the attributes found. If false, returns the String ldapBase,
/// names and values for attributes found.</param> LdapScope scope,
/// <param name="cont">Any controls that apply to the search request. String filter,
/// or null if none.</param> String[] attrs,
/// <seealso cref="LdapConnection.Search"></seealso> Int32 dereference,
public LdapSearchRequest( Int32 maxResults,
string ldapBase, Int32 serverTimeLimit,
LdapScope scope, Boolean typesOnly,
string filter, LdapControl[] cont)
string[] attrs, : base(
int dereference, LdapOperation.SearchRequest,
int maxResults, new RfcSearchRequest(ldapBase, scope, dereference, maxResults, serverTimeLimit, typesOnly, filter, attrs),
int serverTimeLimit, cont) {
bool typesOnly, }
LdapControl[] cont)
: base( /// <summary>
LdapOperation.SearchRequest, /// Retrieves an Iterator object representing the parsed filter for
new RfcSearchRequest(ldapBase, scope, dereference, maxResults, serverTimeLimit, typesOnly, filter, attrs), /// this search request.
cont) /// The first object returned from the Iterator is an Integer indicating
{ /// the type of filter component. One or more values follow the component
} /// type as subsequent items in the Iterator. The pattern of Integer
/// component type followed by values continues until the end of the
/// <summary> /// filter.
/// Retrieves an Iterator object representing the parsed filter for /// Values returned as a byte array may represent UTF-8 characters or may
/// this search request. /// be binary values. The possible Integer components of a search filter
/// The first object returned from the Iterator is an Integer indicating /// and the associated values that follow are:.
/// the type of filter component. One or more values follow the component /// <ul><li>AND - followed by an Iterator value</li><li>OR - followed by an Iterator value</li><li>NOT - followed by an Iterator value</li><li>
/// type as subsequent items in the Iterator. The pattern of Integer /// EQUALITY_MATCH - followed by the attribute name represented as a
/// component type followed by values continues until the end of the /// String, and by the attribute value represented as a byte array
/// filter. /// </li><li>
/// Values returned as a byte array may represent UTF-8 characters or may /// GREATER_OR_EQUAL - followed by the attribute name represented as a
/// be binary values. The possible Integer components of a search filter /// String, and by the attribute value represented as a byte array
/// and the associated values that follow are:. /// </li><li>
/// <ul><li>AND - followed by an Iterator value</li><li>OR - followed by an Iterator value</li><li>NOT - followed by an Iterator value</li><li> /// LESS_OR_EQUAL - followed by the attribute name represented as a
/// EQUALITY_MATCH - followed by the attribute name represented as a /// String, and by the attribute value represented as a byte array
/// String, and by the attribute value represented as a byte array /// </li><li>
/// </li><li> /// APPROX_MATCH - followed by the attribute name represented as a
/// GREATER_OR_EQUAL - followed by the attribute name represented as a /// String, and by the attribute value represented as a byte array
/// String, and by the attribute value represented as a byte array /// </li><li>PRESENT - followed by a attribute name respresented as a String</li><li>
/// </li><li> /// EXTENSIBLE_MATCH - followed by the name of the matching rule
/// LESS_OR_EQUAL - followed by the attribute name represented as a /// represented as a String, by the attribute name represented
/// String, and by the attribute value represented as a byte array /// as a String, and by the attribute value represented as a
/// </li><li> /// byte array.
/// APPROX_MATCH - followed by the attribute name represented as a /// </li><li>
/// String, and by the attribute value represented as a byte array /// SUBSTRINGS - followed by the attribute name represented as a
/// </li><li>PRESENT - followed by a attribute name respresented as a String</li><li> /// String, by one or more SUBSTRING components (INITIAL, ANY,
/// EXTENSIBLE_MATCH - followed by the name of the matching rule /// or FINAL) followed by the SUBSTRING value.
/// represented as a String, by the attribute name represented /// </li></ul>
/// as a String, and by the attribute value represented as a /// </summary>
/// byte array. /// <value>
/// </li><li> /// The search filter.
/// SUBSTRINGS - followed by the attribute name represented as a /// </value>
/// String, by one or more SUBSTRING components (INITIAL, ANY, public IEnumerator SearchFilter => this.RfcFilter.GetFilterIterator();
/// or FINAL) followed by the SUBSTRING value.
/// </li></ul> /// <summary>
/// </summary> /// Retrieves the Base DN for a search request.
/// <value> /// </summary>
/// The search filter. /// <returns>
/// </value> /// the base DN for a search request.
public IEnumerator SearchFilter => RfcFilter.GetFilterIterator(); /// </returns>
public String DN => this.Asn1Object.RequestDn;
/// <summary>
/// Retrieves the Base DN for a search request. /// <summary>
/// </summary> /// Retrieves the scope of a search request.
/// <returns> /// </summary>
/// the base DN for a search request. /// <value>
/// </returns> /// The scope.
public string DN => Asn1Object.RequestDn; /// </value>
public Int32 Scope => ((Asn1Enumerated)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(1)).IntValue();
/// <summary>
/// Retrieves the scope of a search request. /// <summary>
/// </summary> /// Retrieves the behaviour of dereferencing aliases on a search request.
/// <value> /// </summary>
/// The scope. /// <value>
/// </value> /// The dereference.
public int Scope => ((Asn1Enumerated)((RfcSearchRequest)Asn1Object.Get(1)).Get(1)).IntValue(); /// </value>
public Int32 Dereference => ((Asn1Enumerated)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(2)).IntValue();
/// <summary>
/// Retrieves the behaviour of dereferencing aliases on a search request. /// <summary>
/// </summary> /// Retrieves the maximum number of entries to be returned on a search.
/// <value> /// </summary>
/// The dereference. /// <value>
/// </value> /// The maximum results.
public int Dereference => ((Asn1Enumerated)((RfcSearchRequest)Asn1Object.Get(1)).Get(2)).IntValue(); /// </value>
public Int32 MaxResults => ((Asn1Integer)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(3)).IntValue();
/// <summary>
/// Retrieves the maximum number of entries to be returned on a search. /// <summary>
/// </summary> /// Retrieves the server time limit for a search request.
/// <value> /// </summary>
/// The maximum results. /// <value>
/// </value> /// The server time limit.
public int MaxResults => ((Asn1Integer)((RfcSearchRequest)Asn1Object.Get(1)).Get(3)).IntValue(); /// </value>
public Int32 ServerTimeLimit => ((Asn1Integer)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(4)).IntValue();
/// <summary>
/// Retrieves the server time limit for a search request. /// <summary>
/// </summary> /// Retrieves whether attribute values or only attribute types(names) should
/// <value> /// be returned in a search request.
/// The server time limit. /// </summary>
/// </value> /// <value>
public int ServerTimeLimit => ((Asn1Integer)((RfcSearchRequest)Asn1Object.Get(1)).Get(4)).IntValue(); /// <c>true</c> if [types only]; otherwise, <c>false</c>.
/// </value>
/// <summary> public Boolean TypesOnly => ((Asn1Boolean)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(5)).BooleanValue();
/// Retrieves whether attribute values or only attribute types(names) should
/// be returned in a search request. /// <summary>
/// </summary> /// Retrieves an array of attribute names to request for in a search.
/// <value> /// </summary>
/// <c>true</c> if [types only]; otherwise, <c>false</c>. /// <value>
/// </value> /// The attributes.
public bool TypesOnly => ((Asn1Boolean)((RfcSearchRequest)Asn1Object.Get(1)).Get(5)).BooleanValue(); /// </value>
public String[] Attributes {
/// <summary> get {
/// Retrieves an array of attribute names to request for in a search. RfcAttributeDescriptionList attrs = (RfcAttributeDescriptionList)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(7);
/// </summary> String[] values = new String[attrs.Size()];
/// <value> for(Int32 i = 0; i < values.Length; i++) {
/// The attributes. values[i] = ((Asn1OctetString)attrs.Get(i)).StringValue();
/// </value> }
public string[] Attributes
{ return values;
get }
{ }
var attrs = (RfcAttributeDescriptionList)((RfcSearchRequest)Asn1Object.Get(1)).Get(7);
var values = new string[attrs.Size()]; /// <summary>
for (var i = 0; i < values.Length; i++) /// Creates a string representation of the filter in this search request.
{ /// </summary>
values[i] = ((Asn1OctetString)attrs.Get(i)).StringValue(); /// <value>
} /// The string filter.
/// </value>
return values; public String StringFilter => this.RfcFilter.FilterToString();
}
} /// <summary>
/// Retrieves an SearchFilter object representing a filter for a search request.
/// <summary> /// </summary>
/// Creates a string representation of the filter in this search request. /// <value>
/// </summary> /// The RFC filter.
/// <value> /// </value>
/// The string filter. private RfcFilter RfcFilter => (RfcFilter)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(6);
/// </value> }
public string StringFilter => RfcFilter.FilterToString();
/// <summary>
/// Retrieves an SearchFilter object representing a filter for a search request.
/// </summary>
/// <value>
/// The RFC filter.
/// </value>
private RfcFilter RfcFilter => (RfcFilter)((RfcSearchRequest)Asn1Object.Get(1)).Get(6);
}
} }

View File

@ -1,95 +1,87 @@
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>
/// An LdapSearchResults object is returned from a synchronous search
/// operation. It provides access to all results received during the
/// operation (entries and exceptions).
/// </summary>
/// <seealso cref="LdapConnection.Search"></seealso>
public sealed class LdapSearchResults {
private readonly List<RfcLdapMessage> _messages;
private readonly Int32 _messageId;
/// <summary> /// <summary>
/// An LdapSearchResults object is returned from a synchronous search /// Initializes a new instance of the <see cref="LdapSearchResults" /> class.
/// operation. It provides access to all results received during the
/// operation (entries and exceptions).
/// </summary> /// </summary>
/// <seealso cref="LdapConnection.Search"></seealso> /// <param name="messages">The messages.</param>
public sealed class LdapSearchResults /// <param name="messageId">The message identifier.</param>
{ internal LdapSearchResults(List<RfcLdapMessage> messages, Int32 messageId) {
private readonly List<RfcLdapMessage> _messages; this._messages = messages;
private readonly int _messageId; this._messageId = messageId;
}
/// <summary>
/// Initializes a new instance of the <see cref="LdapSearchResults" /> class. /// <summary>
/// </summary> /// Returns a count of the items in the search result.
/// <param name="messages">The messages.</param> /// Returns a count of the entries and exceptions remaining in the object.
/// <param name="messageId">The message identifier.</param> /// If the search was submitted with a batch size greater than zero,
internal LdapSearchResults(List<RfcLdapMessage> messages, int messageId) /// getCount reports the number of results received so far but not enumerated
{ /// with next(). If batch size equals zero, getCount reports the number of
_messages = messages; /// items received, since the application thread blocks until all results are
_messageId = messageId; /// received.
} /// </summary>
/// <value>
/// <summary> /// The count.
/// Returns a count of the items in the search result. /// </value>
/// Returns a count of the entries and exceptions remaining in the object. public Int32 Count => new List<RfcLdapMessage>(this._messages)
/// If the search was submitted with a batch size greater than zero, .Count(x => x.MessageId == this._messageId && GetResponse(x) is LdapSearchResult);
/// getCount reports the number of results received so far but not enumerated
/// with next(). If batch size equals zero, getCount reports the number of /// <summary>
/// items received, since the application thread blocks until all results are /// Reports if there are more search results.
/// received. /// </summary>
/// </summary> /// <returns>
/// <value> /// true if there are more search results.
/// The count. /// </returns>
/// </value> public Boolean HasMore() => new List<RfcLdapMessage>(this._messages)
public int Count => new List<RfcLdapMessage>(_messages) .Any(x => x.MessageId == this._messageId && GetResponse(x) is LdapSearchResult);
.Count(x => x.MessageId == _messageId && GetResponse(x) is LdapSearchResult);
/// <summary>
/// <summary> /// Returns the next result as an LdapEntry.
/// Reports if there are more search results. /// If automatic referral following is disabled or if a referral
/// </summary> /// was not followed, next() will throw an LdapReferralException
/// <returns> /// when the referral is received.
/// true if there are more search results. /// </summary>
/// </returns> /// <returns>
public bool HasMore() => new List<RfcLdapMessage>(_messages) /// The next search result as an LdapEntry.
.Any(x => x.MessageId == _messageId && GetResponse(x) is LdapSearchResult); /// </returns>
/// <exception cref="ArgumentOutOfRangeException">Next - No more results.</exception>
/// <summary> public LdapEntry Next() {
/// Returns the next result as an LdapEntry. IEnumerable<RfcLdapMessage> list = new List<RfcLdapMessage>(this._messages)
/// If automatic referral following is disabled or if a referral .Where(x => x.MessageId == this._messageId);
/// was not followed, next() will throw an LdapReferralException
/// when the referral is received. foreach(RfcLdapMessage item in list) {
/// </summary> _ = this._messages.Remove(item);
/// <returns> LdapMessage response = GetResponse(item);
/// The next search result as an LdapEntry.
/// </returns> if(response is LdapSearchResult result) {
/// <exception cref="ArgumentOutOfRangeException">Next - No more results.</exception> return result.Entry;
public LdapEntry Next() }
{ }
var list = new List<RfcLdapMessage>(_messages)
.Where(x => x.MessageId == _messageId); throw new ArgumentOutOfRangeException(nameof(Next), "No more results");
}
foreach (var item in list)
{ private static LdapMessage GetResponse(RfcLdapMessage item) {
_messages.Remove(item); switch(item.Type) {
var response = GetResponse(item); case LdapOperation.SearchResponse:
return new LdapSearchResult(item);
if (response is LdapSearchResult result) case LdapOperation.SearchResultReference:
{ return new LdapSearchResultReference(item);
return result.Entry; default:
} return new LdapResponse(item);
} }
}
throw new ArgumentOutOfRangeException(nameof(Next), "No more results"); }
}
private static LdapMessage GetResponse(RfcLdapMessage item)
{
switch (item.Type)
{
case LdapOperation.SearchResponse:
return new LdapSearchResult(item);
case LdapOperation.SearchResultReference:
return new LdapSearchResultReference(item);
default:
return new LdapResponse(item);
}
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,304 +1,292 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.Collections.Generic;
using System;
using System.Collections.Generic; namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// The class performs token processing from strings.
/// </summary>
internal class Tokenizer {
// 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 Boolean _returnDelims;
private List<String> _elements;
private String _source;
/// <summary> /// <summary>
/// The class performs token processing from strings. /// Initializes a new instance of the <see cref="Tokenizer" /> class.
/// Initializes a new class instance with a specified string to process
/// and the specified token delimiters to use.
/// </summary> /// </summary>
internal class Tokenizer /// <param name="source">String to tokenize.</param>
{ /// <param name="delimiters">String containing the delimiters.</param>
// The tokenizer uses the default delimiter set: the space character, the tab character, the newline character, and the carriage-return character /// <param name="retDel">if set to <c>true</c> [ret delete].</param>
private readonly string _delimiters = " \t\n\r"; public Tokenizer(String source, String delimiters, Boolean retDel = false) {
this._elements = new List<String>();
private readonly bool _returnDelims; this._delimiters = delimiters ?? this._delimiters;
this._source = source;
private List<string> _elements; this._returnDelims = retDel;
if(this._returnDelims) {
private string _source; this.Tokenize();
} else {
/// <summary> this._elements.AddRange(source.Split(this._delimiters.ToCharArray()));
/// Initializes a new instance of the <see cref="Tokenizer" /> class. }
/// Initializes a new class instance with a specified string to process
/// and the specified token delimiters to use. this.RemoveEmptyStrings();
/// </summary> }
/// <param name="source">String to tokenize.</param>
/// <param name="delimiters">String containing the delimiters.</param> public Int32 Count => this._elements.Count;
/// <param name="retDel">if set to <c>true</c> [ret delete].</param>
public Tokenizer(string source, string delimiters, bool retDel = false) public Boolean HasMoreTokens() => this._elements.Count > 0;
{
_elements = new List<string>(); public String NextToken() {
_delimiters = delimiters ?? _delimiters; if(this._source == String.Empty) {
_source = source; throw new InvalidOperationException();
_returnDelims = retDel; }
if (_returnDelims)
Tokenize(); String result;
else if(this._returnDelims) {
_elements.AddRange(source.Split(_delimiters.ToCharArray())); this.RemoveEmptyStrings();
RemoveEmptyStrings(); result = this._elements[0];
} this._elements.RemoveAt(0);
return result;
public int Count => _elements.Count; }
public bool HasMoreTokens() => _elements.Count > 0; this._elements = new List<String>();
this._elements.AddRange(this._source.Split(this._delimiters.ToCharArray()));
public string NextToken() this.RemoveEmptyStrings();
{ result = this._elements[0];
if (_source == string.Empty) throw new InvalidOperationException(); this._elements.RemoveAt(0);
this._source = this._source.Remove(this._source.IndexOf(result, StringComparison.Ordinal), result.Length);
string result; this._source = this._source.TrimStart(this._delimiters.ToCharArray());
if (_returnDelims) return result;
{ }
RemoveEmptyStrings();
result = _elements[0]; private void RemoveEmptyStrings() {
_elements.RemoveAt(0); for(Int32 index = 0; index < this._elements.Count; index++) {
return result; if(this._elements[index] != String.Empty) {
} continue;
}
_elements = new List<string>();
_elements.AddRange(_source.Split(_delimiters.ToCharArray())); this._elements.RemoveAt(index);
RemoveEmptyStrings(); index--;
result = _elements[0]; }
_elements.RemoveAt(0); }
_source = _source.Remove(_source.IndexOf(result, StringComparison.Ordinal), result.Length);
_source = _source.TrimStart(_delimiters.ToCharArray()); private void Tokenize() {
return result; String tempstr = this._source;
} if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) < 0 && tempstr.Length > 0) {
this._elements.Add(tempstr);
private void RemoveEmptyStrings() } else if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) < 0 && tempstr.Length <= 0) {
{ return;
for (var index = 0; index < _elements.Count; index++) }
{
if (_elements[index] != string.Empty) continue; while(tempstr.IndexOfAny(this._delimiters.ToCharArray()) >= 0) {
if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) == 0) {
_elements.RemoveAt(index); if(tempstr.Length > 1) {
index--; this._elements.Add(tempstr.Substring(0, 1));
} tempstr = tempstr.Substring(1);
} } else {
tempstr = String.Empty;
private void Tokenize() }
{ } else {
var tempstr = _source; String toks = tempstr.Substring(0, tempstr.IndexOfAny(this._delimiters.ToCharArray()));
if (tempstr.IndexOfAny(_delimiters.ToCharArray()) < 0 && tempstr.Length > 0) this._elements.Add(toks);
{ this._elements.Add(tempstr.Substring(toks.Length, 1));
_elements.Add(tempstr);
} tempstr = tempstr.Length > toks.Length + 1 ? tempstr.Substring(toks.Length + 1) : String.Empty;
else if (tempstr.IndexOfAny(_delimiters.ToCharArray()) < 0 && tempstr.Length <= 0) }
{ }
return;
} if(tempstr.Length > 0) {
this._elements.Add(tempstr);
while (tempstr.IndexOfAny(_delimiters.ToCharArray()) >= 0) }
{ }
if (tempstr.IndexOfAny(_delimiters.ToCharArray()) == 0) }
{
if (tempstr.Length > 1) /// <summary>
{ /// Represents an Ldap Matching Rule Assertion.
_elements.Add(tempstr.Substring(0, 1)); /// <pre>
tempstr = tempstr.Substring(1); /// MatchingRuleAssertion ::= SEQUENCE {
} /// matchingRule [1] MatchingRuleId OPTIONAL,
else /// type [2] AttributeDescription OPTIONAL,
{ /// matchValue [3] AssertionValue,
tempstr = string.Empty; /// dnAttributes [4] BOOLEAN DEFAULT FALSE }
} /// </pre></summary>
} /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
else internal class RfcMatchingRuleAssertion : Asn1Sequence {
{ public RfcMatchingRuleAssertion(
var toks = tempstr.Substring(0, tempstr.IndexOfAny(_delimiters.ToCharArray())); String matchingRule,
_elements.Add(toks); String type,
_elements.Add(tempstr.Substring(toks.Length, 1)); SByte[] matchValue,
Asn1Boolean dnAttributes = null)
tempstr = tempstr.Length > toks.Length + 1 ? tempstr.Substring(toks.Length + 1) : string.Empty; : base(4) {
} if(matchingRule != null) {
} this.Add(new Asn1Tagged(new Asn1Identifier(1), new Asn1OctetString(matchingRule), false));
}
if (tempstr.Length > 0)
{ if(type != null) {
_elements.Add(tempstr); 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
// encode it. (See RFC 2251 5.1 number 4)
if(dnAttributes != null && dnAttributes.BooleanValue()) {
this.Add(new Asn1Tagged(new Asn1Identifier(4), dnAttributes, false));
}
}
}
/// <summary>
/// The AttributeDescriptionList is used to list attributes to be returned in
/// a search request.
/// <pre>
/// AttributeDescriptionList ::= SEQUENCE OF
/// AttributeDescription
/// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
internal class RfcAttributeDescriptionList : Asn1SequenceOf {
public RfcAttributeDescriptionList(String[] attrs)
: base(attrs?.Length ?? 0) {
if(attrs == null) {
return;
}
foreach(String attr in attrs) {
this.Add(attr);
}
}
}
/// <summary>
/// Represents an Ldap Search Request.
/// <pre>
/// SearchRequest ::= [APPLICATION 3] SEQUENCE {
/// baseObject LdapDN,
/// scope ENUMERATED {
/// baseObject (0),
/// singleLevel (1),
/// wholeSubtree (2) },
/// derefAliases ENUMERATED {
/// neverDerefAliases (0),
/// derefInSearching (1),
/// derefFindingBaseObj (2),
/// derefAlways (3) },
/// sizeLimit INTEGER (0 .. maxInt),
/// timeLimit INTEGER (0 .. maxInt),
/// typesOnly BOOLEAN,
/// filter Filter,
/// attributes AttributeDescriptionList }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
internal class RfcSearchRequest : Asn1Sequence, IRfcRequest {
public RfcSearchRequest(
String basePath,
LdapScope scope,
Int32 derefAliases,
Int32 sizeLimit,
Int32 timeLimit,
Boolean typesOnly,
String filter,
String[] attributes)
: base(8) {
this.Add(basePath);
this.Add(new Asn1Enumerated(scope));
this.Add(new Asn1Enumerated(derefAliases));
this.Add(new Asn1Integer(sizeLimit));
this.Add(new Asn1Integer(timeLimit));
this.Add(new Asn1Boolean(typesOnly));
this.Add(new RfcFilter(filter));
this.Add(new RfcAttributeDescriptionList(attributes));
}
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchRequest);
public String GetRequestDN() => ((Asn1OctetString)this.Get(0)).StringValue();
}
/// <summary>
/// Represents an Ldap Substring Filter.
/// <pre>
/// SubstringFilter ::= SEQUENCE {
/// type AttributeDescription,
/// -- at least one must be present
/// substrings SEQUENCE OF CHOICE {
/// initial [0] LdapString,
/// any [1] LdapString,
/// final [2] LdapString } }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcSubstringFilter : Asn1Sequence {
public RfcSubstringFilter(String type, Asn1Object substrings)
: base(2) {
this.Add(type);
this.Add(substrings);
}
}
/// <summary>
/// Represents an Ldap Attribute Value Assertion.
/// <pre>
/// AttributeValueAssertion ::= SEQUENCE {
/// attributeDesc AttributeDescription,
/// assertionValue AssertionValue }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcAttributeValueAssertion : Asn1Sequence {
public RfcAttributeValueAssertion(String ad, SByte[] av)
: base(2) {
this.Add(ad);
this.Add(new Asn1OctetString(av));
}
public String AttributeDescription => ((Asn1OctetString)this.Get(0)).StringValue();
public SByte[] AssertionValue => ((Asn1OctetString)this.Get(1)).ByteValue();
}
/// <summary> Encapsulates an Ldap Bind properties.</summary>
internal class BindProperties {
/// <summary> /// <summary>
/// Represents an Ldap Matching Rule Assertion. /// Initializes a new instance of the <see cref="BindProperties" /> class.
/// <pre>
/// MatchingRuleAssertion ::= SEQUENCE {
/// matchingRule [1] MatchingRuleId OPTIONAL,
/// type [2] AttributeDescription OPTIONAL,
/// matchValue [3] AssertionValue,
/// dnAttributes [4] BOOLEAN DEFAULT FALSE }
/// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcMatchingRuleAssertion : Asn1Sequence
{
public RfcMatchingRuleAssertion(
string matchingRule,
string type,
sbyte[] matchValue,
Asn1Boolean dnAttributes = null)
: base(4)
{
if (matchingRule != null)
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 dnAttributes if false, that is the default value and we must not
// encode it. (See RFC 2251 5.1 number 4)
if (dnAttributes != null && dnAttributes.BooleanValue())
Add(new Asn1Tagged(new Asn1Identifier(4), dnAttributes, false));
}
}
/// <summary>
/// The AttributeDescriptionList is used to list attributes to be returned in
/// a search request.
/// <pre>
/// AttributeDescriptionList ::= SEQUENCE OF
/// AttributeDescription
/// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
internal class RfcAttributeDescriptionList : Asn1SequenceOf
{
public RfcAttributeDescriptionList(string[] attrs)
: base(attrs?.Length ?? 0)
{
if (attrs == null) return;
foreach (var attr in attrs)
{
Add(attr);
}
}
}
/// <summary>
/// Represents an Ldap Search Request.
/// <pre>
/// SearchRequest ::= [APPLICATION 3] SEQUENCE {
/// baseObject LdapDN,
/// scope ENUMERATED {
/// baseObject (0),
/// singleLevel (1),
/// wholeSubtree (2) },
/// derefAliases ENUMERATED {
/// neverDerefAliases (0),
/// derefInSearching (1),
/// derefFindingBaseObj (2),
/// derefAlways (3) },
/// sizeLimit INTEGER (0 .. maxInt),
/// timeLimit INTEGER (0 .. maxInt),
/// typesOnly BOOLEAN,
/// filter Filter,
/// attributes AttributeDescriptionList }
/// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <param name="version">The version.</param>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" /> /// <param name="dn">The dn.</param>
internal class RfcSearchRequest : Asn1Sequence, IRfcRequest /// <param name="method">The method.</param>
{ /// <param name="anonymous">if set to <c>true</c> [anonymous].</param>
public RfcSearchRequest( public BindProperties(
string basePath, Int32 version,
LdapScope scope, String dn,
int derefAliases, String method,
int sizeLimit, Boolean anonymous) {
int timeLimit, this.ProtocolVersion = version;
bool typesOnly, this.AuthenticationDN = dn;
string filter, this.AuthenticationMethod = method;
string[] attributes) this.Anonymous = anonymous;
: base(8) }
{
Add(basePath); public Int32 ProtocolVersion {
Add(new Asn1Enumerated(scope)); get;
Add(new Asn1Enumerated(derefAliases)); }
Add(new Asn1Integer(sizeLimit));
Add(new Asn1Integer(timeLimit)); public String AuthenticationDN {
Add(new Asn1Boolean(typesOnly)); get;
Add(new RfcFilter(filter)); }
Add(new RfcAttributeDescriptionList(attributes));
} public String AuthenticationMethod {
get;
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchRequest); }
public string GetRequestDN() => ((Asn1OctetString) Get(0)).StringValue(); public Boolean Anonymous {
} get;
}
/// <summary> }
/// Represents an Ldap Substring Filter.
/// <pre>
/// SubstringFilter ::= SEQUENCE {
/// type AttributeDescription,
/// -- at least one must be present
/// substrings SEQUENCE OF CHOICE {
/// initial [0] LdapString,
/// any [1] LdapString,
/// final [2] LdapString } }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcSubstringFilter : Asn1Sequence
{
public RfcSubstringFilter(string type, Asn1Object substrings)
: base(2)
{
Add(type);
Add(substrings);
}
}
/// <summary>
/// Represents an Ldap Attribute Value Assertion.
/// <pre>
/// AttributeValueAssertion ::= SEQUENCE {
/// attributeDesc AttributeDescription,
/// assertionValue AssertionValue }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcAttributeValueAssertion : Asn1Sequence
{
public RfcAttributeValueAssertion(string ad, sbyte[] av)
: base(2)
{
Add(ad);
Add(new Asn1OctetString(av));
}
public string AttributeDescription => ((Asn1OctetString) Get(0)).StringValue();
public sbyte[] AssertionValue => ((Asn1OctetString) Get(1)).ByteValue();
}
/// <summary> Encapsulates an Ldap Bind properties.</summary>
internal class BindProperties
{
/// <summary>
/// Initializes a new instance of the <see cref="BindProperties" /> class.
/// </summary>
/// <param name="version">The version.</param>
/// <param name="dn">The dn.</param>
/// <param name="method">The method.</param>
/// <param name="anonymous">if set to <c>true</c> [anonymous].</param>
public BindProperties(
int version,
string dn,
string method,
bool anonymous)
{
ProtocolVersion = version;
AuthenticationDN = dn;
AuthenticationMethod = method;
Anonymous = anonymous;
}
public int ProtocolVersion { get; }
public string AuthenticationDN { get; }
public string AuthenticationMethod { get; }
public bool Anonymous { get; }
}
} }

View File

@ -1,131 +1,118 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// Represents an Ldap Control.
/// <pre>
/// Control ::= SEQUENCE {
/// controlType LdapOID,
/// criticality BOOLEAN DEFAULT FALSE,
/// controlValue OCTET STRING OPTIONAL }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcControl : Asn1Sequence {
/// <summary> /// <summary>
/// Represents an Ldap Control. /// Initializes a new instance of the <see cref="RfcControl"/> class.
/// <pre> /// Note: criticality is only added if true, as per RFC 2251 sec 5.1 part
/// Control ::= SEQUENCE { /// (4): If a value of a type is its default value, it MUST be
/// controlType LdapOID, /// absent.
/// criticality BOOLEAN DEFAULT FALSE,
/// controlValue OCTET STRING OPTIONAL }
/// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <param name="controlType">Type of the control.</param>
internal class RfcControl : Asn1Sequence /// <param name="criticality">The criticality.</param>
{ /// <param name="controlValue">The control value.</param>
/// <summary> public RfcControl(String controlType, Asn1Boolean criticality = null, Asn1Object controlValue = null)
/// Initializes a new instance of the <see cref="RfcControl"/> class. : base(3) {
/// Note: criticality is only added if true, as per RFC 2251 sec 5.1 part this.Add(controlType);
/// (4): If a value of a type is its default value, it MUST be this.Add(criticality ?? new Asn1Boolean(false));
/// absent.
/// </summary> if(controlValue != null) {
/// <param name="controlType">Type of the control.</param> this.Add(controlValue);
/// <param name="criticality">The criticality.</param> }
/// <param name="controlValue">The control value.</param> }
public RfcControl(string controlType, Asn1Boolean criticality = null, Asn1Object controlValue = null)
: base(3) public RfcControl(Asn1Structured seqObj)
{ : base(3) {
Add(controlType); for(Int32 i = 0; i < seqObj.Size(); i++) {
Add(criticality ?? new Asn1Boolean(false)); this.Add(seqObj.Get(i));
}
if (controlValue != null) }
Add(controlValue);
} public Asn1OctetString ControlType => (Asn1OctetString)this.Get(0);
public RfcControl(Asn1Structured seqObj) public Asn1Boolean Criticality => this.Size() > 1 && this.Get(1) is Asn1Boolean boolean ? boolean : new Asn1Boolean(false);
: base(3)
{ public Asn1OctetString ControlValue {
for (var i = 0; i < seqObj.Size(); i++) get {
Add(seqObj.Get(i)); if(this.Size() > 2) {
} // MUST be a control value
return (Asn1OctetString)this.Get(2);
public Asn1OctetString ControlType => (Asn1OctetString)Get(0); }
public Asn1Boolean Criticality => Size() > 1 && Get(1) is Asn1Boolean boolean ? boolean : new Asn1Boolean(false); return this.Size() > 1 && this.Get(1) is Asn1OctetString s ? s : null;
}
public Asn1OctetString ControlValue set {
{ if(value == null) {
get return;
{ }
if (Size() > 2)
{ if(this.Size() == 3) {
// MUST be a control value // We already have a control value, replace it
return (Asn1OctetString)Get(2); this.Set(2, value);
} return;
}
return Size() > 1 && Get(1) is Asn1OctetString s ? s : null;
} if(this.Size() == 2) {
set // Get the second element
{ Asn1Object obj = this.Get(1);
if (value == null)
return; // Is this a control value
if(obj is Asn1OctetString) {
if (Size() == 3) // replace this one
{ this.Set(1, value);
// We already have a control value, replace it } else {
Set(2, value); // add a new one at the end
return; this.Add(value);
} }
}
if (Size() == 2) }
{ }
// Get the second element }
var obj = Get(1);
/// <summary>
// Is this a control value /// Represents Ldap Sasl Credentials.
if (obj is Asn1OctetString) /// <pre>
{ /// SaslCredentials ::= SEQUENCE {
// replace this one /// mechanism LdapString,
Set(1, value); /// credentials OCTET STRING OPTIONAL }
} /// </pre></summary>
else /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
{ internal class RfcSaslCredentials : Asn1Sequence {
// add a new one at the end public RfcSaslCredentials(String mechanism, SByte[] credentials = null)
Add(value); : base(2) {
} this.Add(mechanism);
} if(credentials != null) {
} this.Add(new Asn1OctetString(credentials));
} }
} }
}
/// <summary>
/// Represents Ldap Sasl Credentials. /// <summary>
/// <pre> /// Represents an Ldap Authentication Choice.
/// SaslCredentials ::= SEQUENCE { /// <pre>
/// mechanism LdapString, /// AuthenticationChoice ::= CHOICE {
/// credentials OCTET STRING OPTIONAL } /// simple [0] OCTET STRING,
/// </pre></summary> /// -- 1 and 2 reserved
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// sasl [3] SaslCredentials }
internal class RfcSaslCredentials : Asn1Sequence /// </pre></summary>
{ /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Choice" />
public RfcSaslCredentials(string mechanism, sbyte[] credentials = null) internal class RfcAuthenticationChoice : Asn1Choice {
: base(2) public RfcAuthenticationChoice(SByte[] passwd)
{ : base(new Asn1Tagged(new Asn1Identifier(0), new Asn1OctetString(passwd), false)) {
Add(mechanism); }
if (credentials != null)
Add(new Asn1OctetString(credentials)); public RfcAuthenticationChoice(String mechanism, SByte[] credentials)
} : base(new Asn1Tagged(new Asn1Identifier(3, true), new RfcSaslCredentials(mechanism, credentials), false)) {
} // implicit tagging
}
/// <summary> }
/// Represents an Ldap Authentication Choice.
/// <pre>
/// AuthenticationChoice ::= CHOICE {
/// simple [0] OCTET STRING,
/// -- 1 and 2 reserved
/// sasl [3] SaslCredentials }
/// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Choice" />
internal class RfcAuthenticationChoice : Asn1Choice
{
public RfcAuthenticationChoice(sbyte[] passwd)
: base(new Asn1Tagged(new Asn1Identifier(0), new Asn1OctetString(passwd), false))
{
}
public RfcAuthenticationChoice(string mechanism, sbyte[] credentials)
: base(new Asn1Tagged(new Asn1Identifier(3, true), new RfcSaslCredentials(mechanism, credentials), false))
{
// implicit tagging
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,246 +1,238 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.IO;
using System.IO;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// Encapsulates a single search result that is in response to an asynchronous
/// search operation.
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
internal class LdapSearchResult : LdapMessage {
private LdapEntry _entry;
internal LdapSearchResult(RfcLdapMessage message)
: base(message) {
}
public LdapEntry Entry {
get {
if(this._entry != null) {
return this._entry;
}
LdapAttributeSet attrs = new LdapAttributeSet();
RfcSearchResultEntry entry = (RfcSearchResultEntry)this.Message.Response;
foreach(Asn1Object o in entry.Attributes.ToArray()) {
Asn1Sequence seq = (Asn1Sequence)o;
LdapAttribute attr = new LdapAttribute(((Asn1OctetString)seq.Get(0)).StringValue());
Asn1Set set = (Asn1Set)seq.Get(1);
foreach(Asn1Object t in set.ToArray()) {
attr.AddValue(((Asn1OctetString)t).ByteValue());
}
_ = attrs.Add(attr);
}
this._entry = new LdapEntry(entry.ObjectName, attrs);
return this._entry;
}
}
public override String ToString() => this._entry?.ToString() ?? base.ToString();
}
/// <summary>
/// Represents an Ldap Search Result Reference.
/// <pre>
/// SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LdapURL
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
internal class RfcSearchResultReference : Asn1SequenceOf {
/// <summary> /// <summary>
/// Encapsulates a single search result that is in response to an asynchronous /// Initializes a new instance of the <see cref="RfcSearchResultReference"/> class.
/// search operation. /// The only time a client will create a SearchResultReference is when it is
/// decoding it from an Stream.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" /> /// <param name="stream">The streab.</param>
internal class LdapSearchResult : LdapMessage /// <param name="len">The length.</param>
{ public RfcSearchResultReference(Stream stream, Int32 len)
private LdapEntry _entry; : base(stream, len) {
}
internal LdapSearchResult(RfcLdapMessage message)
: base(message) public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResultReference);
{ }
}
/// <summary>
public LdapEntry Entry /// Represents an Ldap Extended Response.
{ /// <pre>
get /// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
{ /// COMPONENTS OF LdapResult,
if (_entry != null) return _entry; /// responseName [10] LdapOID OPTIONAL,
/// response [11] OCTET STRING OPTIONAL }
var attrs = new LdapAttributeSet(); /// </pre>
var entry = (RfcSearchResultEntry) Message.Response; /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
foreach (var o in entry.Attributes.ToArray()) /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
{ internal class RfcExtendedResponse : Asn1Sequence, IRfcResponse {
var seq = (Asn1Sequence) o; public const Int32 ResponseNameCode = 10;
var attr = new LdapAttribute(((Asn1OctetString)seq.Get(0)).StringValue());
var set = (Asn1Set)seq.Get(1); public const Int32 ResponseCode = 11;
foreach (var t in set.ToArray()) private readonly Int32 _referralIndex;
{ private readonly Int32 _responseNameIndex;
attr.AddValue(((Asn1OctetString)t).ByteValue()); private readonly Int32 _responseIndex;
}
attrs.Add(attr);
}
_entry = new LdapEntry(entry.ObjectName, attrs);
return _entry;
}
}
public override string ToString() => _entry?.ToString() ?? base.ToString();
}
/// <summary> /// <summary>
/// Represents an Ldap Search Result Reference. /// Initializes a new instance of the <see cref="RfcExtendedResponse"/> class.
/// <pre> /// The only time a client will create a ExtendedResponse is when it is
/// SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LdapURL /// decoding it from an stream.
/// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" /> /// <param name="stream">The stream.</param>
internal class RfcSearchResultReference : Asn1SequenceOf /// <param name="len">The length.</param>
{ public RfcExtendedResponse(Stream stream, Int32 len)
/// <summary> : base(stream, len) {
/// Initializes a new instance of the <see cref="RfcSearchResultReference"/> class. if(this.Size() <= 3) {
/// The only time a client will create a SearchResultReference is when it is return;
/// decoding it from an Stream. }
/// </summary>
/// <param name="stream">The streab.</param> for(Int32 i = 3; i < this.Size(); i++) {
/// <param name="len">The length.</param> Asn1Tagged obj = (Asn1Tagged)this.Get(i);
public RfcSearchResultReference(Stream stream, int len) Asn1Identifier id = obj.GetIdentifier();
: base(stream, len)
{ switch(id.Tag) {
} case RfcLdapResult.Referral:
SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResultReference);
} using(MemoryStream bais = new MemoryStream(content.ToByteArray())) {
this.Set(i, new Asn1SequenceOf(bais, content.Length));
}
this._referralIndex = i;
break;
case ResponseNameCode:
this.Set(i, new Asn1OctetString(((Asn1OctetString)obj.TaggedValue).ByteValue()));
this._responseNameIndex = i;
break;
case ResponseCode:
this.Set(i, obj.TaggedValue);
this._responseIndex = i;
break;
}
}
}
public Asn1OctetString ResponseName => this._responseNameIndex != 0 ? (Asn1OctetString)this.Get(this._responseNameIndex) : null;
public Asn1OctetString Response => this._responseIndex != 0 ? (Asn1OctetString)this.Get(this._responseIndex) : 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._referralIndex != 0 ? (Asn1SequenceOf)this.Get(this._referralIndex) : null;
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ExtendedResponse);
}
/// <summary>
/// Represents and Ldap Bind Response.
/// <pre>
/// BindResponse ::= [APPLICATION 1] SEQUENCE {
/// COMPONENTS OF LdapResult,
/// serverSaslCreds [7] OCTET STRING OPTIONAL }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
internal class RfcBindResponse : Asn1Sequence, IRfcResponse {
/// <summary> /// <summary>
/// Represents an Ldap Extended Response. /// Initializes a new instance of the <see cref="RfcBindResponse"/> class.
/// <pre> /// The only time a client will create a BindResponse is when it is
/// ExtendedResponse ::= [APPLICATION 24] SEQUENCE { /// decoding it from an InputStream
/// COMPONENTS OF LdapResult, /// Note: If serverSaslCreds is included in the BindResponse, it does not
/// responseName [10] LdapOID OPTIONAL, /// need to be decoded since it is already an OCTET STRING.
/// response [11] OCTET STRING OPTIONAL }
/// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <param name="stream">The in renamed.</param>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" /> /// <param name="len">The length.</param>
internal class RfcExtendedResponse : Asn1Sequence, IRfcResponse public RfcBindResponse(Stream stream, Int32 len)
{ : base(stream, len) {
public const int ResponseNameCode = 10; // Decode optional referral from Asn1OctetString to Referral.
if(this.Size() <= 3) {
public const int ResponseCode = 11; return;
}
private readonly int _referralIndex;
private readonly int _responseNameIndex; Asn1Tagged obj = (Asn1Tagged)this.Get(3);
private readonly int _responseIndex;
if(obj.GetIdentifier().Tag != RfcLdapResult.Referral) {
/// <summary> return;
/// Initializes a new instance of the <see cref="RfcExtendedResponse"/> class. }
/// The only time a client will create a ExtendedResponse is when it is
/// decoding it from an stream. SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
/// </summary>
/// <param name="stream">The stream.</param> using(MemoryStream bais = new MemoryStream(content.ToByteArray())) {
/// <param name="len">The length.</param> this.Set(3, new Asn1SequenceOf(bais, content.Length));
public RfcExtendedResponse(Stream stream, int len) }
: base(stream, len) }
{
if (Size() <= 3) return; public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0);
for (var i = 3; i < Size(); i++) public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue());
{
var obj = (Asn1Tagged) Get(i); public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue());
var id = obj.GetIdentifier();
public Asn1SequenceOf GetReferral() => this.Size() > 3 && this.Get(3) is Asn1SequenceOf ? (Asn1SequenceOf)this.Get(3) : null;
switch (id.Tag)
{ public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.BindResponse);
case RfcLdapResult.Referral: }
var content = ((Asn1OctetString) obj.TaggedValue).ByteValue();
/// <summary>
using (var bais = new MemoryStream(content.ToByteArray())) /// Represents an LDAP Intermediate Response.
Set(i, new Asn1SequenceOf(bais, content.Length)); /// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
/// COMPONENTS OF LDAPResult, note: only present on incorrectly
_referralIndex = i; /// encoded response from pre Falcon-sp1 server
break; /// responseName [10] LDAPOID OPTIONAL,
case ResponseNameCode: /// responseValue [11] OCTET STRING OPTIONAL }.
Set(i, new Asn1OctetString(((Asn1OctetString) obj.TaggedValue).ByteValue())); /// </summary>
_responseNameIndex = i; /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
break; /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
case ResponseCode: internal class RfcIntermediateResponse : Asn1Sequence, IRfcResponse {
Set(i, obj.TaggedValue); public const Int32 TagResponseName = 0;
_responseIndex = i; public const Int32 TagResponse = 1;
break;
} public RfcIntermediateResponse(Stream stream, Int32 len)
} : base(stream, len) {
} Int32 i = this.Size() >= 3 ? 3 : 0;
public Asn1OctetString ResponseName => _responseNameIndex != 0 ? (Asn1OctetString) Get(_responseNameIndex) : null; for(; i < this.Size(); i++) {
Asn1Tagged obj = (Asn1Tagged)this.Get(i);
public Asn1OctetString Response => _responseIndex != 0 ? (Asn1OctetString) Get(_responseIndex) : null;
switch(obj.GetIdentifier().Tag) {
public Asn1Enumerated GetResultCode() => (Asn1Enumerated) Get(0); case TagResponseName:
this.Set(i, new Asn1OctetString(((Asn1OctetString)obj.TaggedValue).ByteValue()));
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue()); break;
case TagResponse:
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue()); this.Set(i, obj.TaggedValue);
break;
public Asn1SequenceOf GetReferral() }
=> _referralIndex != 0 ? (Asn1SequenceOf) Get(_referralIndex) : null; }
}
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ExtendedResponse);
} public Asn1Enumerated GetResultCode() => this.Size() > 3 ? (Asn1Enumerated)this.Get(0) : null;
/// <summary> public Asn1OctetString GetMatchedDN() => this.Size() > 3 ? new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue()) : null;
/// Represents and Ldap Bind Response.
/// <pre> public Asn1OctetString GetErrorMessage() =>
/// BindResponse ::= [APPLICATION 1] SEQUENCE { this.Size() > 3 ? new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue()) : null;
/// COMPONENTS OF LdapResult,
/// serverSaslCreds [7] OCTET STRING OPTIONAL } public Asn1SequenceOf GetReferral() => this.Size() > 3 ? (Asn1SequenceOf)this.Get(3) : null;
/// </pre>
/// </summary> public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.IntermediateResponse);
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> }
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
internal class RfcBindResponse : Asn1Sequence, IRfcResponse
{
/// <summary>
/// Initializes a new instance of the <see cref="RfcBindResponse"/> class.
/// The only time a client will create a BindResponse is when it is
/// decoding it from an InputStream
/// Note: If serverSaslCreds is included in the BindResponse, it does not
/// need to be decoded since it is already an OCTET STRING.
/// </summary>
/// <param name="stream">The in renamed.</param>
/// <param name="len">The length.</param>
public RfcBindResponse(Stream stream, int len)
: base(stream, len)
{
// Decode optional referral from Asn1OctetString to Referral.
if (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);
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue());
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue());
public Asn1SequenceOf GetReferral() => Size() > 3 && Get(3) is Asn1SequenceOf ? (Asn1SequenceOf) Get(3) : null;
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.BindResponse);
}
/// <summary>
/// Represents an LDAP Intermediate Response.
/// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
/// COMPONENTS OF LDAPResult, note: only present on incorrectly
/// encoded response from pre Falcon-sp1 server
/// responseName [10] LDAPOID OPTIONAL,
/// responseValue [11] OCTET STRING OPTIONAL }.
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
internal class RfcIntermediateResponse : Asn1Sequence, IRfcResponse
{
public const int TagResponseName = 0;
public const int TagResponse = 1;
public RfcIntermediateResponse(Stream stream, int len)
: base(stream, len)
{
var i = Size() >= 3 ? 3 : 0;
for (; i < Size(); i++)
{
var obj = (Asn1Tagged) Get(i);
switch (obj.GetIdentifier().Tag)
{
case TagResponseName:
Set(i, new Asn1OctetString(((Asn1OctetString) obj.TaggedValue).ByteValue()));
break;
case TagResponse:
Set(i, obj.TaggedValue);
break;
}
}
}
public Asn1Enumerated GetResultCode() => Size() > 3 ? (Asn1Enumerated) Get(0) : null;
public Asn1OctetString GetMatchedDN() => Size() > 3 ? new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue()) : null;
public Asn1OctetString GetErrorMessage() =>
Size() > 3 ? new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue()) : null;
public Asn1SequenceOf GetReferral() => Size() > 3 ? (Asn1SequenceOf) Get(3) : null;
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.IntermediateResponse);
}
} }

View File

@ -1,390 +1,379 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.IO;
using System;
using System.IO; namespace Unosquare.Swan.Networking.Ldap {
/// <summary>
/// Represents an Ldap Message.
/// <pre>
/// LdapMessage ::= SEQUENCE {
/// messageID MessageID,
/// protocolOp CHOICE {
/// bindRequest BindRequest,
/// bindResponse BindResponse,
/// unbindRequest UnbindRequest,
/// searchRequest SearchRequest,
/// searchResEntry SearchResultEntry,
/// searchResDone SearchResultDone,
/// searchResRef SearchResultReference,
/// modifyRequest ModifyRequest,
/// modifyResponse ModifyResponse,
/// addRequest AddRequest,
/// addResponse AddResponse,
/// delRequest DelRequest,
/// delResponse DelResponse,
/// modDNRequest ModifyDNRequest,
/// modDNResponse ModifyDNResponse,
/// compareRequest CompareRequest,
/// compareResponse CompareResponse,
/// abandonRequest AbandonRequest,
/// extendedReq ExtendedRequest,
/// extendedResp ExtendedResponse },
/// controls [0] Controls OPTIONAL }
/// </pre>
/// Note: The creation of a MessageID should be hidden within the creation of
/// an RfcLdapMessage. The MessageID needs to be in sequence, and has an
/// upper and lower limit. There is never a case when a user should be
/// able to specify the MessageID for an RfcLdapMessage. The MessageID()
/// constructor should be package protected. (So the MessageID value
/// isn't arbitrarily run up.).
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal sealed class RfcLdapMessage : Asn1Sequence {
private readonly Asn1Object _op;
/// <summary> /// <summary>
/// Represents an Ldap Message. /// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
/// <pre> /// Create an RfcLdapMessage request from input parameters.
/// LdapMessage ::= SEQUENCE {
/// messageID MessageID,
/// protocolOp CHOICE {
/// bindRequest BindRequest,
/// bindResponse BindResponse,
/// unbindRequest UnbindRequest,
/// searchRequest SearchRequest,
/// searchResEntry SearchResultEntry,
/// searchResDone SearchResultDone,
/// searchResRef SearchResultReference,
/// modifyRequest ModifyRequest,
/// modifyResponse ModifyResponse,
/// addRequest AddRequest,
/// addResponse AddResponse,
/// delRequest DelRequest,
/// delResponse DelResponse,
/// modDNRequest ModifyDNRequest,
/// modDNResponse ModifyDNResponse,
/// compareRequest CompareRequest,
/// compareResponse CompareResponse,
/// abandonRequest AbandonRequest,
/// extendedReq ExtendedRequest,
/// extendedResp ExtendedResponse },
/// controls [0] Controls OPTIONAL }
/// </pre>
/// Note: The creation of a MessageID should be hidden within the creation of
/// an RfcLdapMessage. The MessageID needs to be in sequence, and has an
/// upper and lower limit. There is never a case when a user should be
/// able to specify the MessageID for an RfcLdapMessage. The MessageID()
/// constructor should be package protected. (So the MessageID value
/// isn't arbitrarily run up.).
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <param name="op">The op.</param>
internal sealed class RfcLdapMessage : Asn1Sequence /// <param name="controls">The controls.</param>
{ public RfcLdapMessage(IRfcRequest op, RfcControls controls)
private readonly Asn1Object _op; : base(3) {
this._op = (Asn1Object)op;
/// <summary>
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class. this.Add(new RfcMessageID()); // MessageID has static counter
/// Create an RfcLdapMessage request from input parameters. this.Add((Asn1Object)op);
/// </summary> if(controls != null) {
/// <param name="op">The op.</param> this.Add(controls);
/// <param name="controls">The controls.</param> }
public RfcLdapMessage(IRfcRequest op, RfcControls controls) }
: base(3)
{
_op = (Asn1Object) op;
Add(new RfcMessageID()); // MessageID has static counter
Add((Asn1Object) op);
if (controls != null)
{
Add(controls);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
/// Will decode an RfcLdapMessage directly from an InputStream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="len">The length.</param>
/// <exception cref="Exception">RfcLdapMessage: Invalid tag: " + protocolOpId.Tag.</exception>
public RfcLdapMessage(Stream stream, int len)
: base(stream, len)
{
// Decode implicitly tagged protocol operation from an Asn1Tagged type
// to its appropriate application type.
var protocolOp = (Asn1Tagged) Get(1);
var protocolOpId = protocolOp.GetIdentifier();
var content = ((Asn1OctetString) protocolOp.TaggedValue).ByteValue();
var bais = new MemoryStream(content.ToByteArray());
switch ((LdapOperation) protocolOpId.Tag)
{
case LdapOperation.SearchResponse:
Set(1, new RfcSearchResultEntry(bais, content.Length));
break;
case LdapOperation.SearchResult:
Set(1, new RfcSearchResultDone(bais, content.Length));
break;
case LdapOperation.SearchResultReference:
Set(1, new RfcSearchResultReference(bais, content.Length));
break;
case LdapOperation.BindResponse:
Set(1, new RfcBindResponse(bais, content.Length));
break;
case LdapOperation.ExtendedResponse:
Set(1, new RfcExtendedResponse(bais, content.Length));
break;
case LdapOperation.IntermediateResponse:
Set(1, new RfcIntermediateResponse(bais, content.Length));
break;
case LdapOperation.ModifyResponse:
Set(1, new RfcModifyResponse(bais, content.Length));
break;
default:
throw new InvalidOperationException($"RfcLdapMessage: Invalid tag: {protocolOpId.Tag}");
}
// decode optional implicitly tagged controls from Asn1Tagged type to
// to RFC 2251 types.
if (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();
/// <summary> Returns this RfcLdapMessage's message type.</summary>
public LdapOperation Type => (LdapOperation) Get(1).GetIdentifier().Tag;
public Asn1Object Response => Get(1);
public string RequestDn => ((IRfcRequest) _op).GetRequestDN();
public LdapMessage RequestingMessage { get; set; }
public IRfcRequest GetRequest() => (IRfcRequest) Get(1);
public bool IsRequest() => Get(1) is IRfcRequest;
}
/// <summary> /// <summary>
/// Represents Ldap Controls. /// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
/// <pre> /// Will decode an RfcLdapMessage directly from an InputStream.
/// Controls ::= SEQUENCE OF Control
/// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" /> /// <param name="stream">The stream.</param>
internal class RfcControls : Asn1SequenceOf /// <param name="len">The length.</param>
{ /// <exception cref="Exception">RfcLdapMessage: Invalid tag: " + protocolOpId.Tag.</exception>
public const int Controls = 0; [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
public RfcLdapMessage(Stream stream, Int32 len)
public RfcControls() : base(stream, len) {
: base(5) // Decode implicitly tagged protocol operation from an Asn1Tagged type
{ // to its appropriate application type.
} Asn1Tagged protocolOp = (Asn1Tagged)this.Get(1);
Asn1Identifier protocolOpId = protocolOp.GetIdentifier();
public RfcControls(Stream stream, int len) SByte[] content = ((Asn1OctetString)protocolOp.TaggedValue).ByteValue();
: base(stream, len) MemoryStream bais = new MemoryStream(content.ToByteArray());
{
// Convert each SEQUENCE element to a Control switch((LdapOperation)protocolOpId.Tag) {
for (var i = 0; i < Size(); i++) case LdapOperation.SearchResponse:
{ this.Set(1, new RfcSearchResultEntry(bais, content.Length));
var tempControl = new RfcControl((Asn1Sequence) Get(i)); break;
Set(i, tempControl);
} case LdapOperation.SearchResult:
} this.Set(1, new RfcSearchResultDone(bais, content.Length));
break;
public void Add(RfcControl control) => base.Add(control);
case LdapOperation.SearchResultReference:
public void Set(int index, RfcControl control) => base.Set(index, control); this.Set(1, new RfcSearchResultReference(bais, content.Length));
break;
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(Controls, true);
} case LdapOperation.BindResponse:
this.Set(1, new RfcBindResponse(bais, content.Length));
break;
case LdapOperation.ExtendedResponse:
this.Set(1, new RfcExtendedResponse(bais, content.Length));
break;
case LdapOperation.IntermediateResponse:
this.Set(1, new RfcIntermediateResponse(bais, content.Length));
break;
case LdapOperation.ModifyResponse:
this.Set(1, new RfcModifyResponse(bais, content.Length));
break;
default:
throw new InvalidOperationException($"RfcLdapMessage: Invalid tag: {protocolOpId.Tag}");
}
// decode optional implicitly tagged controls from Asn1Tagged type to
// to RFC 2251 types.
if(this.Size() <= 2) {
return;
}
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>
public LdapOperation Type => (LdapOperation)this.Get(1).GetIdentifier().Tag;
public Asn1Object Response => this.Get(1);
public String RequestDn => ((IRfcRequest)this._op).GetRequestDN();
public LdapMessage RequestingMessage {
get; set;
}
public IRfcRequest GetRequest() => (IRfcRequest)this.Get(1);
public Boolean IsRequest() => this.Get(1) is IRfcRequest;
}
/// <summary>
/// Represents Ldap Controls.
/// <pre>
/// Controls ::= SEQUENCE OF Control
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
internal class RfcControls : Asn1SequenceOf {
public const Int32 Controls = 0;
public RfcControls()
: base(5) {
}
public RfcControls(Stream stream, Int32 len)
: base(stream, len) {
// Convert each SEQUENCE element to a Control
for(Int32 i = 0; i < this.Size(); i++) {
RfcControl tempControl = new RfcControl((Asn1Sequence)this.Get(i));
this.Set(i, tempControl);
}
}
public void Add(RfcControl control) => base.Add(control);
public void Set(Int32 index, RfcControl control) => base.Set(index, control);
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(Controls, true);
}
/// <summary>
/// This interface represents RfcLdapMessages that contain a response from a
/// server.
/// If the protocol operation of the RfcLdapMessage is of this type,
/// it contains at least an RfcLdapResult.
/// </summary>
internal interface IRfcResponse {
/// <summary> /// <summary>
/// This interface represents RfcLdapMessages that contain a response from a /// Gets the result code.
/// server.
/// If the protocol operation of the RfcLdapMessage is of this type,
/// it contains at least an RfcLdapResult.
/// </summary> /// </summary>
internal interface IRfcResponse /// <returns>Asn1Enumerated.</returns>
{ Asn1Enumerated GetResultCode();
/// <summary>
/// Gets the result code.
/// </summary>
/// <returns>Asn1Enumerated.</returns>
Asn1Enumerated GetResultCode();
/// <summary>
/// Gets the matched dn.
/// </summary>
/// <returns>RfcLdapDN.</returns>
Asn1OctetString GetMatchedDN();
/// <summary>
/// Gets the error message.
/// </summary>
/// <returns>RfcLdapString.</returns>
Asn1OctetString GetErrorMessage();
/// <summary>
/// Gets the referral.
/// </summary>
/// <returns>Asn1SequenceOf.</returns>
Asn1SequenceOf GetReferral();
}
/// <summary> /// <summary>
/// This interface represents Protocol Operations that are requests from a /// Gets the matched dn.
/// client.
/// </summary> /// </summary>
internal interface IRfcRequest /// <returns>RfcLdapDN.</returns>
{ Asn1OctetString GetMatchedDN();
/// <summary>
/// Builds a new request using the data from the this object.
/// </summary>
/// <returns>A <see cref="System.String" />.</returns>
string GetRequestDN();
}
/// <summary> /// <summary>
/// Represents an LdapResult. /// Gets the error message.
/// <pre>
/// LdapResult ::= SEQUENCE {
/// resultCode ENUMERATED {
/// success (0),
/// operationsError (1),
/// protocolError (2),
/// timeLimitExceeded (3),
/// sizeLimitExceeded (4),
/// compareFalse (5),
/// compareTrue (6),
/// authMethodNotSupported (7),
/// strongAuthRequired (8),
/// -- 9 reserved --
/// referral (10), -- new
/// adminLimitExceeded (11), -- new
/// unavailableCriticalExtension (12), -- new
/// confidentialityRequired (13), -- new
/// saslBindInProgress (14), -- new
/// noSuchAttribute (16),
/// undefinedAttributeType (17),
/// inappropriateMatching (18),
/// constraintViolation (19),
/// attributeOrValueExists (20),
/// invalidAttributeSyntax (21),
/// -- 22-31 unused --
/// noSuchObject (32),
/// aliasProblem (33),
/// invalidDNSyntax (34),
/// -- 35 reserved for undefined isLeaf --
/// aliasDereferencingProblem (36),
/// -- 37-47 unused --
/// inappropriateAuthentication (48),
/// invalidCredentials (49),
/// insufficientAccessRights (50),
/// busy (51),
/// unavailable (52),
/// unwillingToPerform (53),
/// loopDetect (54),
/// -- 55-63 unused --
/// namingViolation (64),
/// objectClassViolation (65),
/// notAllowedOnNonLeaf (66),
/// notAllowedOnRDN (67),
/// entryAlreadyExists (68),
/// objectClassModsProhibited (69),
/// -- 70 reserved for CLdap --
/// affectsMultipleDSAs (71), -- new
/// -- 72-79 unused --
/// other (80) },
/// -- 81-90 reserved for APIs --
/// matchedDN LdapDN,
/// errorMessage LdapString,
/// referral [3] Referral OPTIONAL }
/// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <returns>RfcLdapString.</returns>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" /> Asn1OctetString GetErrorMessage();
internal class RfcLdapResult : Asn1Sequence, IRfcResponse
{
public const int Referral = 3;
public RfcLdapResult(Stream stream, int len)
: base(stream, len)
{
// Decode optional referral from Asn1OctetString to Referral.
if (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);
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue());
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue());
public Asn1SequenceOf GetReferral() => Size() > 3 ? (Asn1SequenceOf) Get(3) : null;
}
/// <summary> /// <summary>
/// Represents an Ldap Search Result Done Response. /// Gets the referral.
/// <pre>
/// SearchResultDone ::= [APPLICATION 5] LdapResult
/// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.RfcLdapResult" /> /// <returns>Asn1SequenceOf.</returns>
internal class RfcSearchResultDone : RfcLdapResult Asn1SequenceOf GetReferral();
{ }
public RfcSearchResultDone(Stream stream, int len)
: base(stream, len) /// <summary>
{ /// This interface represents Protocol Operations that are requests from a
} /// client.
/// </summary>
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResult); internal interface IRfcRequest {
}
/// <summary> /// <summary>
/// Represents an Ldap Search Result Entry. /// Builds a new request using the data from the this object.
/// <pre>
/// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
/// objectName LdapDN,
/// attributes PartialAttributeList }
/// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <returns>A <see cref="System.String" />.</returns>
internal sealed class RfcSearchResultEntry : Asn1Sequence String GetRequestDN();
{ }
public RfcSearchResultEntry(Stream stream, int len)
: base(stream, len) /// <summary>
{ /// Represents an LdapResult.
} /// <pre>
/// LdapResult ::= SEQUENCE {
public string ObjectName => ((Asn1OctetString) Get(0)).StringValue(); /// resultCode ENUMERATED {
/// success (0),
public Asn1Sequence Attributes => (Asn1Sequence) Get(1); /// operationsError (1),
/// protocolError (2),
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResponse); /// timeLimitExceeded (3),
} /// sizeLimitExceeded (4),
/// compareFalse (5),
/// compareTrue (6),
/// authMethodNotSupported (7),
/// strongAuthRequired (8),
/// -- 9 reserved --
/// referral (10), -- new
/// adminLimitExceeded (11), -- new
/// unavailableCriticalExtension (12), -- new
/// confidentialityRequired (13), -- new
/// saslBindInProgress (14), -- new
/// noSuchAttribute (16),
/// undefinedAttributeType (17),
/// inappropriateMatching (18),
/// constraintViolation (19),
/// attributeOrValueExists (20),
/// invalidAttributeSyntax (21),
/// -- 22-31 unused --
/// noSuchObject (32),
/// aliasProblem (33),
/// invalidDNSyntax (34),
/// -- 35 reserved for undefined isLeaf --
/// aliasDereferencingProblem (36),
/// -- 37-47 unused --
/// inappropriateAuthentication (48),
/// invalidCredentials (49),
/// insufficientAccessRights (50),
/// busy (51),
/// unavailable (52),
/// unwillingToPerform (53),
/// loopDetect (54),
/// -- 55-63 unused --
/// namingViolation (64),
/// objectClassViolation (65),
/// notAllowedOnNonLeaf (66),
/// notAllowedOnRDN (67),
/// entryAlreadyExists (68),
/// objectClassModsProhibited (69),
/// -- 70 reserved for CLdap --
/// affectsMultipleDSAs (71), -- new
/// -- 72-79 unused --
/// other (80) },
/// -- 81-90 reserved for APIs --
/// matchedDN LdapDN,
/// errorMessage LdapString,
/// referral [3] Referral OPTIONAL }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
internal class RfcLdapResult : Asn1Sequence, IRfcResponse {
public const Int32 Referral = 3;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
public RfcLdapResult(Stream stream, Int32 len)
: base(stream, len) {
// Decode optional referral from Asn1OctetString to Referral.
if(this.Size() <= 3) {
return;
}
Asn1Tagged obj = (Asn1Tagged)this.Get(3);
Asn1Identifier id = obj.GetIdentifier();
if(id.Tag != Referral) {
return;
}
SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
this.Set(3, new Asn1SequenceOf(new MemoryStream(content.ToByteArray()), 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 ? (Asn1SequenceOf)this.Get(3) : null;
}
/// <summary>
/// Represents an Ldap Search Result Done Response.
/// <pre>
/// SearchResultDone ::= [APPLICATION 5] LdapResult
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.RfcLdapResult" />
internal class RfcSearchResultDone : RfcLdapResult {
public RfcSearchResultDone(Stream stream, Int32 len)
: base(stream, len) {
}
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResult);
}
/// <summary>
/// Represents an Ldap Search Result Entry.
/// <pre>
/// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
/// objectName LdapDN,
/// attributes PartialAttributeList }
/// </pre>
/// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal sealed class RfcSearchResultEntry : Asn1Sequence {
public RfcSearchResultEntry(Stream stream, Int32 len)
: base(stream, len) {
}
public String ObjectName => ((Asn1OctetString)this.Get(0)).StringValue();
public Asn1Sequence Attributes => (Asn1Sequence)this.Get(1);
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResponse);
}
/// <summary>
/// Represents an Ldap Message ID.
/// <pre>
/// MessageID ::= INTEGER (0 .. maxInt)
/// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
/// Note: The creation of a MessageID should be hidden within the creation of
/// an RfcLdapMessage. The MessageID needs to be in sequence, and has an
/// upper and lower limit. There is never a case when a user should be
/// able to specify the MessageID for an RfcLdapMessage. The MessageID()
/// class should be package protected. (So the MessageID value isn't
/// arbitrarily run up.)
/// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Integer" />
internal class RfcMessageID : Asn1Integer {
private static Int32 _messageId;
private static readonly Object SyncRoot = new Object();
/// <summary> /// <summary>
/// Represents an Ldap Message ID. /// Initializes a new instance of the <see cref="RfcMessageID"/> class.
/// <pre> /// Creates a MessageID with an auto incremented Asn1Integer value.
/// MessageID ::= INTEGER (0 .. maxInt) /// Bounds: (0 .. 2,147,483,647) (2^^31 - 1 or Integer.MAX_VALUE)
/// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) -- /// MessageID zero is never used in this implementation. Always
/// Note: The creation of a MessageID should be hidden within the creation of /// start the messages with one.
/// an RfcLdapMessage. The MessageID needs to be in sequence, and has an /// </summary>
/// upper and lower limit. There is never a case when a user should be protected internal RfcMessageID()
/// able to specify the MessageID for an RfcLdapMessage. The MessageID() : base(MessageId) {
/// class should be package protected. (So the MessageID value isn't }
/// arbitrarily run up.)
/// </pre></summary> private static Int32 MessageId {
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Integer" /> get {
internal class RfcMessageID : Asn1Integer lock(SyncRoot) {
{ return _messageId < Int32.MaxValue ? ++_messageId : (_messageId = 1);
private static int _messageId; }
private static readonly object SyncRoot = new object(); }
}
/// <summary> }
/// Initializes a new instance of the <see cref="RfcMessageID"/> class.
/// Creates a MessageID with an auto incremented Asn1Integer value.
/// Bounds: (0 .. 2,147,483,647) (2^^31 - 1 or Integer.MAX_VALUE)
/// MessageID zero is never used in this implementation. Always
/// start the messages with one.
/// </summary>
protected internal RfcMessageID()
: base(MessageId)
{
}
private static int MessageId
{
get
{
lock (SyncRoot)
{
return _messageId < int.MaxValue ? ++_messageId : (_messageId = 1);
}
}
}
}
} }

View File

@ -1,46 +1,42 @@
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>
/// ModifyRequest ::= [APPLICATION 6] SEQUENCE { /// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
/// object LdapDN, /// object LdapDN,
/// modification SEQUENCE OF SEQUENCE { /// modification SEQUENCE OF SEQUENCE {
/// operation ENUMERATED { /// operation ENUMERATED {
/// add (0), /// add (0),
/// delete (1), /// delete (1),
/// replace (2) }, /// replace (2) },
/// modification AttributeTypeAndValues } } /// modification AttributeTypeAndValues } }
/// </pre> /// </pre>
/// </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 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)this.Get(1);
public Asn1SequenceOf Modifications => (Asn1SequenceOf)Get(1); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyRequest);
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyRequest); public String GetRequestDN() => ((Asn1OctetString)this.Get(0)).StringValue();
}
public string GetRequestDN() => ((Asn1OctetString)Get(0)).StringValue();
} internal class RfcModifyResponse : RfcLdapResult {
public RfcModifyResponse(Stream stream, Int32 len)
internal class RfcModifyResponse : RfcLdapResult : base(stream, len) {
{ }
public RfcModifyResponse(Stream stream, int len)
: base(stream, len) public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyResponse);
{ }
}
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyResponse);
}
} }

View File

@ -1,397 +1,390 @@
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>
/// Represents a basic SMTP client that is capable of submitting messages to an SMTP server.
/// </summary>
/// <example>
/// The following code explains how to send a simple e-mail.
/// <code>
/// using System.Net.Mail;
///
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new SmtpClient("smtp.gmail.com", 587);
///
/// // send an email
/// client.SendMailAsync(
/// new MailMessage("sender@test.com", "recipient@test.cm", "Subject", "Body"));
/// }
/// }
/// </code>
///
/// The following code demonstrates how to sent an e-mail using a SmtpSessionState:
/// <code>
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new SmtpClient("smtp.gmail.com", 587);
///
/// // create a new session state with a sender address
/// var session = new SmtpSessionState { SenderAddress = "sender@test.com" };
///
/// // add a recipient
/// session.Recipients.Add("recipient@test.cm");
///
/// // send
/// client.SendMailAsync(session);
/// }
/// }
/// </code>
///
/// The following code shows how to send an e-mail with an attachment:
/// <code>
/// using System.Net.Mail;
///
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new SmtpClient("smtp.gmail.com", 587);
///
/// // create a new session state with a sender address
/// var session = new SmtpSessionState { SenderAddress = "sender@test.com" };
///
/// // add a recipient
/// session.Recipients.Add("recipient@test.cm");
///
/// // load a file as an attachment
/// var attachment = new MimePart("image", "gif")
/// {
/// Content = new
/// MimeContent(File.OpenRead("meme.gif"), ContentEncoding.Default),
/// ContentDisposition =
/// new ContentDisposition(ContentDisposition.Attachment),
/// ContentTransferEncoding = ContentEncoding.Base64,
/// FileName = Path.GetFileName("meme.gif")
/// };
///
/// // send
/// client.SendMailAsync(session);
/// }
/// }
/// </code>
/// </example>
public class SmtpClient {
/// <summary> /// <summary>
/// Represents a basic SMTP client that is capable of submitting messages to an SMTP server. /// Initializes a new instance of the <see cref="SmtpClient" /> class.
/// </summary> /// </summary>
/// <example> /// <param name="host">The host.</param>
/// The following code explains how to send a simple e-mail. /// <param name="port">The port.</param>
/// <code> /// <exception cref="ArgumentNullException">host.</exception>
/// using System.Net.Mail; public SmtpClient(String host, Int32 port) {
/// this.Host = host ?? throw new ArgumentNullException(nameof(host));
/// class Example this.Port = port;
/// { this.ClientHostname = Network.HostName;
/// static void Main() }
/// {
/// // create a new smtp client using google's smtp server /// <summary>
/// var client = new SmtpClient("smtp.gmail.com", 587); /// Gets or sets the credentials. No credentials will be used if set to null.
/// /// </summary>
/// // send an email /// <value>
/// client.SendMailAsync( /// The credentials.
/// new MailMessage("sender@test.com", "recipient@test.cm", "Subject", "Body")); /// </value>
/// } public NetworkCredential Credentials {
/// } get; set;
/// </code> }
///
/// The following code demonstrates how to sent an e-mail using a SmtpSessionState: /// <summary>
/// <code> /// Gets the host.
/// class Example /// </summary>
/// { /// <value>
/// static void Main() /// The host.
/// { /// </value>
/// // create a new smtp client using google's smtp server public String Host {
/// var client = new SmtpClient("smtp.gmail.com", 587); get;
/// }
/// // create a new session state with a sender address
/// var session = new SmtpSessionState { SenderAddress = "sender@test.com" }; /// <summary>
/// /// Gets the port.
/// // add a recipient /// </summary>
/// session.Recipients.Add("recipient@test.cm"); /// <value>
/// /// The port.
/// // send /// </value>
/// client.SendMailAsync(session); public Int32 Port {
/// } get;
/// } }
/// </code>
/// /// <summary>
/// The following code shows how to send an e-mail with an attachment: /// Gets or sets a value indicating whether the SSL is enabled.
/// <code> /// If set to false, communication between client and server will not be secured.
/// using System.Net.Mail; /// </summary>
/// /// <value>
/// class Example /// <c>true</c> if [enable SSL]; otherwise, <c>false</c>.
/// { /// </value>
/// static void Main() public Boolean EnableSsl {
/// { get; set;
/// // create a new smtp client using google's smtp server }
/// var client = new SmtpClient("smtp.gmail.com", 587);
/// /// <summary>
/// // create a new session state with a sender address /// Gets or sets the name of the client that gets announced to the server.
/// var session = new SmtpSessionState { SenderAddress = "sender@test.com" }; /// </summary>
/// /// <value>
/// // add a recipient /// The client hostname.
/// session.Recipients.Add("recipient@test.cm"); /// </value>
/// public String ClientHostname {
/// // load a file as an attachment get; set;
/// var attachment = new MimePart("image", "gif") }
/// {
/// Content = new
/// MimeContent(File.OpenRead("meme.gif"), ContentEncoding.Default),
/// ContentDisposition =
/// new ContentDisposition(ContentDisposition.Attachment),
/// ContentTransferEncoding = ContentEncoding.Base64,
/// FileName = Path.GetFileName("meme.gif")
/// };
///
/// // send
/// client.SendMailAsync(session);
/// }
/// }
/// </code>
/// </example>
public class SmtpClient
{
/// <summary>
/// Initializes a new instance of the <see cref="SmtpClient" /> class.
/// </summary>
/// <param name="host">The host.</param>
/// <param name="port">The port.</param>
/// <exception cref="ArgumentNullException">host.</exception>
public SmtpClient(string host, int port)
{
Host = host ?? throw new ArgumentNullException(nameof(host));
Port = port;
ClientHostname = Network.HostName;
}
/// <summary>
/// Gets or sets the credentials. No credentials will be used if set to null.
/// </summary>
/// <value>
/// The credentials.
/// </value>
public NetworkCredential Credentials { get; set; }
/// <summary>
/// Gets the host.
/// </summary>
/// <value>
/// The host.
/// </value>
public string Host { get; }
/// <summary>
/// Gets the port.
/// </summary>
/// <value>
/// The port.
/// </value>
public int Port { get; }
/// <summary>
/// Gets or sets a value indicating whether the SSL is enabled.
/// If set to false, communication between client and server will not be secured.
/// </summary>
/// <value>
/// <c>true</c> if [enable SSL]; otherwise, <c>false</c>.
/// </value>
public bool EnableSsl { get; set; }
/// <summary>
/// Gets or sets the name of the client that gets announced to the server.
/// </summary>
/// <value>
/// The client hostname.
/// </value>
public string ClientHostname { get; set; }
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
/// <summary> /// <summary>
/// Sends an email message asynchronously. /// Sends an email message asynchronously.
/// </summary> /// </summary>
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
/// <param name="sessionId">The session identifier.</param> /// <param name="sessionId">The session identifier.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="callback">The callback.</param> /// <param name="callback">The callback.</param>
/// <returns> /// <returns>
/// 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>
public Task SendMailAsync( [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
MailMessage message, public Task SendMailAsync(
string sessionId = null, MailMessage message,
CancellationToken ct = default, String sessionId = null,
RemoteCertificateValidationCallback callback = null) CancellationToken ct = default,
{ 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 = Credentials == null ? string.Empty : SmtpDefinitions.SmtpAuthMethods.Login, AuthMode = this.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(MailAddress recipient in message.To) {
foreach (var 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 this.SendMailAsync(state, sessionId, ct, callback);
}
return SendMailAsync(state, sessionId, ct, callback);
}
#endif #endif
/// <summary> /// <summary>
/// Sends an email message using a session state object. /// Sends an email message using a session state object.
/// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but /// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but
/// rather from the properties of this class. /// rather from the properties of this class.
/// </summary> /// </summary>
/// <param name="sessionState">The state.</param> /// <param name="sessionState">The state.</param>
/// <param name="sessionId">The session identifier.</param> /// <param name="sessionId">The session identifier.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="callback">The callback.</param> /// <param name="callback">The callback.</param>
/// <returns> /// <returns>
/// 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">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>
/// Sends an array of email messages using a session state object. /// Sends an array of email messages using a session state object.
/// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but /// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but
/// rather from the properties of this class. /// rather from the properties of this class.
/// </summary> /// </summary>
/// <param name="sessionStates">The session states.</param> /// <param name="sessionStates">The session states.</param>
/// <param name="sessionId">The session identifier.</param> /// <param name="sessionId">The session identifier.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <param name="callback">The callback.</param> /// <param name="callback">The callback.</param>
/// <returns> /// <returns>
/// 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">sessionStates.</exception> /// <exception cref="ArgumentNullException">sessionStates.</exception>
/// <exception cref="SecurityException">Could not upgrade the channel to SSL.</exception> /// <exception cref="SecurityException">Could not upgrade the channel to SSL.</exception>
/// <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(Connection connection = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 1000)) {
using (var connection = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 1000)) SmtpSender sender = new SmtpSender(sessionId);
{
var sender = new SmtpSender(sessionId); try {
// Read the greeting message
try sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
{
// Read the greeting message // EHLO 1
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); await this.SendEhlo(ct, sender, connection).ConfigureAwait(false);
// EHLO 1 // STARTTLS
await SendEhlo(ct, sender, connection).ConfigureAwait(false); if(this.EnableSsl) {
sender.RequestText = $"{SmtpCommandNames.STARTTLS}";
// STARTTLS
if (EnableSsl) await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
{ sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
sender.RequestText = $"{SmtpCommandNames.STARTTLS}"; sender.ValidateReply();
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false); if(await connection.UpgradeToSecureAsClientAsync(callback: callback).ConfigureAwait(false) == false) {
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); throw new SecurityException("Could not upgrade the channel to SSL.");
sender.ValidateReply(); }
}
if (await connection.UpgradeToSecureAsClientAsync(callback: callback).ConfigureAwait(false) == false)
throw new SecurityException("Could not upgrade the channel to SSL."); // EHLO 2
} await this.SendEhlo(ct, sender, connection).ConfigureAwait(false);
// EHLO 2 // AUTH
await SendEhlo(ct, sender, connection).ConfigureAwait(false); if(this.Credentials != null) {
ConnectionAuth auth = new ConnectionAuth(connection, sender, this.Credentials);
// AUTH await auth.AuthenticateAsync(ct).ConfigureAwait(false);
if (Credentials != null) }
{
var auth = new ConnectionAuth(connection, sender, Credentials); foreach(SmtpSessionState sessionState in sessionStates) {
await auth.AuthenticateAsync(ct).ConfigureAwait(false); {
} // MAIL FROM
sender.RequestText = $"{SmtpCommandNames.MAIL} FROM:<{sessionState.SenderAddress}>";
foreach (var sessionState in sessionStates)
{ await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
{ sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
// MAIL FROM sender.ValidateReply();
sender.RequestText = $"{SmtpCommandNames.MAIL} FROM:<{sessionState.SenderAddress}>"; }
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false); // RCPT TO
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); foreach(String recipient in sessionState.Recipients) {
sender.ValidateReply(); sender.RequestText = $"{SmtpCommandNames.RCPT} TO:<{recipient}>";
}
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
// RCPT TO sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
foreach (var recipient in sessionState.Recipients) sender.ValidateReply();
{ }
sender.RequestText = $"{SmtpCommandNames.RCPT} TO:<{recipient}>";
{
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false); // DATA
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); sender.RequestText = $"{SmtpCommandNames.DATA}";
sender.ValidateReply();
} await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
{ sender.ValidateReply();
// DATA }
sender.RequestText = $"{SmtpCommandNames.DATA}";
{
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false); // CONTENT
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); String dataTerminator = sessionState.DataBuffer
sender.ValidateReply(); .Skip(sessionState.DataBuffer.Count - 5)
} .ToText();
{ sender.RequestText = $"Buffer ({sessionState.DataBuffer.Count} bytes)";
// CONTENT
var dataTerminator = sessionState.DataBuffer await connection.WriteDataAsync(sessionState.DataBuffer.ToArray(), true, ct).ConfigureAwait(false);
.Skip(sessionState.DataBuffer.Count - 5) if(dataTerminator.EndsWith(SmtpDefinitions.SmtpDataCommandTerminator) == false) {
.ToText(); await connection.WriteTextAsync(SmtpDefinitions.SmtpDataCommandTerminator, ct).ConfigureAwait(false);
}
sender.RequestText = $"Buffer ({sessionState.DataBuffer.Count} bytes)";
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
await connection.WriteDataAsync(sessionState.DataBuffer.ToArray(), true, ct).ConfigureAwait(false); sender.ValidateReply();
if (dataTerminator.EndsWith(SmtpDefinitions.SmtpDataCommandTerminator) == false) }
await connection.WriteTextAsync(SmtpDefinitions.SmtpDataCommandTerminator, ct).ConfigureAwait(false); }
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); {
sender.ValidateReply(); // QUIT
} sender.RequestText = $"{SmtpCommandNames.QUIT}";
}
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
{ sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
// QUIT sender.ValidateReply();
sender.RequestText = $"{SmtpCommandNames.QUIT}"; }
} catch(Exception ex) {
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false); String errorMessage =
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); $"Could not send email. {ex.Message}\r\n Last Request: {sender.RequestText}\r\n Last Reply: {sender.ReplyText}";
sender.ValidateReply(); errorMessage.Error(typeof(SmtpClient).FullName, sessionId);
}
} throw new SmtpException(errorMessage);
catch (Exception ex) }
{ }
var errorMessage = }
$"Could not send email. {ex.Message}\r\n Last Request: {sender.RequestText}\r\n Last Reply: {sender.ReplyText}"; }
errorMessage.Error(typeof(SmtpClient).FullName, sessionId);
private async Task SendEhlo(CancellationToken ct, SmtpSender sender, Connection connection) {
throw new SmtpException(errorMessage); sender.RequestText = $"{SmtpCommandNames.EHLO} {this.ClientHostname}";
}
} await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
}
} do {
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
private async Task SendEhlo(CancellationToken ct, SmtpSender sender, Connection connection) } while(!sender.IsReplyOk);
{
sender.RequestText = $"{SmtpCommandNames.EHLO} {ClientHostname}"; sender.ValidateReply();
}
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
private class ConnectionAuth {
do private readonly SmtpSender _sender;
{ private readonly Connection _connection;
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); private readonly NetworkCredential _credentials;
} while (!sender.IsReplyOk);
public ConnectionAuth(Connection connection, SmtpSender sender, NetworkCredential credentials) {
sender.ValidateReply(); this._connection = connection;
} this._sender = sender;
this._credentials = credentials;
private class ConnectionAuth }
{
private readonly SmtpSender _sender; public async Task AuthenticateAsync(CancellationToken ct) {
private readonly Connection _connection; this._sender.RequestText =
private readonly NetworkCredential _credentials; $"{SmtpCommandNames.AUTH} {SmtpDefinitions.SmtpAuthMethods.Login} {Convert.ToBase64String(Encoding.UTF8.GetBytes(this._credentials.UserName))}";
public ConnectionAuth(Connection connection, SmtpSender sender, NetworkCredential credentials) await this._connection.WriteLineAsync(this._sender.RequestText, ct).ConfigureAwait(false);
{ this._sender.ReplyText = await this._connection.ReadLineAsync(ct).ConfigureAwait(false);
_connection = connection; this._sender.ValidateReply();
_sender = sender; this._sender.RequestText = Convert.ToBase64String(Encoding.UTF8.GetBytes(this._credentials.Password));
_credentials = credentials;
} await this._connection.WriteLineAsync(this._sender.RequestText, ct).ConfigureAwait(false);
this._sender.ReplyText = await this._connection.ReadLineAsync(ct).ConfigureAwait(false);
public async Task AuthenticateAsync(CancellationToken ct) this._sender.ValidateReply();
{ }
_sender.RequestText = }
$"{SmtpCommandNames.AUTH} {SmtpDefinitions.SmtpAuthMethods.Login} {Convert.ToBase64String(Encoding.UTF8.GetBytes(_credentials.UserName))}"; }
await _connection.WriteLineAsync(_sender.RequestText, ct).ConfigureAwait(false);
_sender.ReplyText = await _connection.ReadLineAsync(ct).ConfigureAwait(false);
_sender.ValidateReply();
_sender.RequestText = Convert.ToBase64String(Encoding.UTF8.GetBytes(_credentials.Password));
await _connection.WriteLineAsync(_sender.RequestText, ct).ConfigureAwait(false);
_sender.ReplyText = await _connection.ReadLineAsync(ct).ConfigureAwait(false);
_sender.ValidateReply();
}
}
}
} }

View File

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

View File

@ -1,60 +1,55 @@
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) => this._sessionId = sessionId;
public SmtpSender(string sessionId)
{ public String RequestText {
_sessionId = sessionId; get => this._requestText;
} set {
this._requestText = value;
public string RequestText $" TX {this._requestText}".Debug(typeof(SmtpClient), this._sessionId);
{ }
get => _requestText; }
set
{ public String ReplyText {
_requestText = value; get; set;
$" TX {_requestText}".Debug(typeof(SmtpClient), _sessionId); }
}
} public Boolean IsReplyOk => this.ReplyText.StartsWith("250 ");
public string ReplyText { get; set; } public void ValidateReply() {
if(this.ReplyText == null) {
public bool IsReplyOk => ReplyText.StartsWith("250 "); throw new SmtpException("There was no response from the server");
}
public void ValidateReply()
{ try {
if (ReplyText == null) SmtpServerReply response = SmtpServerReply.Parse(this.ReplyText);
throw new SmtpException("There was no response from the server"); $" RX {this.ReplyText} - {response.IsPositive}".Debug(typeof(SmtpClient), this._sessionId);
try if(response.IsPositive) {
{ return;
var response = SmtpServerReply.Parse(ReplyText); }
$" RX {ReplyText} - {response.IsPositive}".Debug(typeof(SmtpClient), _sessionId);
String responseContent = String.Empty;
if (response.IsPositive) return; if(response.Content.Count > 0) {
responseContent = String.Join(";", response.Content.ToArray());
var responseContent = string.Empty; }
if (response.Content.Count > 0)
responseContent = string.Join(";", response.Content.ToArray()); throw new SmtpException((SmtpStatusCode)response.ReplyCode, responseContent);
} catch {
throw new SmtpException((SmtpStatusCode)response.ReplyCode, responseContent); throw new SmtpException($"Could not parse server response: {this.ReplyText}");
} }
catch }
{ }
throw new SmtpException($"Could not parse server response: {ReplyText}");
}
}
}
} }

View File

@ -1,243 +1,267 @@
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>
/// Represents an SMTP server response object.
/// </summary>
public class SmtpServerReply {
#region Constructors
/// <summary> /// <summary>
/// Represents an SMTP server response object. /// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary> /// </summary>
public class SmtpServerReply /// <param name="responseCode">The response code.</param>
{ /// <param name="statusCode">The status code.</param>
#region Constructors /// <param name="content">The content.</param>
public SmtpServerReply(Int32 responseCode, String statusCode, params String[] content) {
/// <summary> this.Content = new List<String>();
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class. this.ReplyCode = responseCode;
/// </summary> this.EnhancedStatusCode = statusCode;
/// <param name="responseCode">The response code.</param> this.Content.AddRange(content);
/// <param name="statusCode">The status code.</param> this.IsValid = responseCode >= 200 && responseCode < 600;
/// <param name="content">The content.</param> this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
public SmtpServerReply(int responseCode, string statusCode, params string[] content) this.ReplyCodeCategory = SmtpReplyCodeCategories.Unknown;
{
Content = new List<string>(); if(!this.IsValid) {
ReplyCode = responseCode; return;
EnhancedStatusCode = statusCode; }
Content.AddRange(content);
IsValid = responseCode >= 200 && responseCode < 600; if(responseCode >= 200) {
ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown; this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion;
ReplyCodeCategory = SmtpReplyCodeCategories.Unknown; }
if (!IsValid) return; if(responseCode >= 300) {
if (responseCode >= 200) ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion; this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate;
if (responseCode >= 300) ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate; }
if (responseCode >= 400) ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative;
if (responseCode >= 500) ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative; if(responseCode >= 400) {
if (responseCode >= 600) ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown; this.ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative;
}
if (int.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out var middleDigit))
{ if(responseCode >= 500) {
if (middleDigit >= 0 && middleDigit <= 5) this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative;
ReplyCodeCategory = (SmtpReplyCodeCategories) middleDigit; }
}
} if(responseCode >= 600) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
/// <summary> }
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary> if(Int32.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out Int32 middleDigit)) {
public SmtpServerReply() if(middleDigit >= 0 && middleDigit <= 5) {
: this(0, string.Empty, string.Empty) this.ReplyCodeCategory = (SmtpReplyCodeCategories)middleDigit;
{ }
// placeholder }
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class. /// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary> /// </summary>
/// <param name="responseCode">The response code.</param> public SmtpServerReply()
/// <param name="statusCode">The status code.</param> : this(0, String.Empty, String.Empty) {
/// <param name="content">The content.</param> // placeholder
public SmtpServerReply(int responseCode, string statusCode, string content) }
: this(responseCode, statusCode, new[] {content})
{ /// <summary>
} /// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
/// <summary> /// <param name="responseCode">The response code.</param>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class. /// <param name="statusCode">The status code.</param>
/// </summary> /// <param name="content">The content.</param>
/// <param name="responseCode">The response code.</param> public SmtpServerReply(Int32 responseCode, String statusCode, String content)
/// <param name="content">The content.</param> : this(responseCode, statusCode, new[] { content }) {
public SmtpServerReply(int responseCode, string content) }
: this(responseCode, string.Empty, content)
{ /// <summary>
} /// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
#endregion /// <param name="responseCode">The response code.</param>
/// <param name="content">The content.</param>
#region Pre-built responses (https://tools.ietf.org/html/rfc5321#section-4.2.2) public SmtpServerReply(Int32 responseCode, String content)
: this(responseCode, String.Empty, content) {
/// <summary> }
/// Gets the command unrecognized reply.
/// </summary> #endregion
public static SmtpServerReply CommandUnrecognized =>
new SmtpServerReply(500, "Syntax error, command unrecognized"); #region Pre-built responses (https://tools.ietf.org/html/rfc5321#section-4.2.2)
/// <summary> /// <summary>
/// Gets the syntax error arguments reply. /// Gets the command unrecognized reply.
/// </summary> /// </summary>
public static SmtpServerReply SyntaxErrorArguments => public static SmtpServerReply CommandUnrecognized =>
new SmtpServerReply(501, "Syntax error in parameters or arguments"); new SmtpServerReply(500, "Syntax error, command unrecognized");
/// <summary> /// <summary>
/// Gets the command not implemented reply. /// Gets the syntax error arguments reply.
/// </summary> /// </summary>
public static SmtpServerReply CommandNotImplemented => new SmtpServerReply(502, "Command not implemented"); public static SmtpServerReply SyntaxErrorArguments =>
new SmtpServerReply(501, "Syntax error in parameters or arguments");
/// <summary>
/// Gets the bad sequence of commands reply. /// <summary>
/// </summary> /// Gets the command not implemented reply.
public static SmtpServerReply BadSequenceOfCommands => new SmtpServerReply(503, "Bad sequence of commands"); /// </summary>
public static SmtpServerReply CommandNotImplemented => new SmtpServerReply(502, "Command not implemented");
/// <summary>
/// Gets the protocol violation reply. /// <summary>
/// </summary>= /// Gets the bad sequence of commands reply.
public static SmtpServerReply ProtocolViolation => /// </summary>
new SmtpServerReply(451, "Requested action aborted: error in processing"); public static SmtpServerReply BadSequenceOfCommands => new SmtpServerReply(503, "Bad sequence of commands");
/// <summary> /// <summary>
/// Gets the system status bye reply. /// Gets the protocol violation reply.
/// </summary> /// </summary>=
public static SmtpServerReply SystemStatusBye => public static SmtpServerReply ProtocolViolation =>
new SmtpServerReply(221, "Service closing transmission channel"); new SmtpServerReply(451, "Requested action aborted: error in processing");
/// <summary> /// <summary>
/// Gets the system status help reply. /// Gets the system status bye reply.
/// </summary>= /// </summary>
public static SmtpServerReply SystemStatusHelp => new SmtpServerReply(221, "Refer to RFC 5321"); public static SmtpServerReply SystemStatusBye =>
new SmtpServerReply(221, "Service closing transmission channel");
/// <summary>
/// Gets the bad syntax command empty reply. /// <summary>
/// </summary> /// Gets the system status help reply.
public static SmtpServerReply BadSyntaxCommandEmpty => new SmtpServerReply(400, "Error: bad syntax"); /// </summary>=
public static SmtpServerReply SystemStatusHelp => new SmtpServerReply(221, "Refer to RFC 5321");
/// <summary>
/// Gets the OK reply. /// <summary>
/// </summary> /// Gets the bad syntax command empty reply.
public static SmtpServerReply Ok => new SmtpServerReply(250, "OK"); /// </summary>
public static SmtpServerReply BadSyntaxCommandEmpty => new SmtpServerReply(400, "Error: bad syntax");
/// <summary>
/// Gets the authorization required reply. /// <summary>
/// </summary> /// Gets the OK reply.
public static SmtpServerReply AuthorizationRequired => new SmtpServerReply(530, "Authorization Required"); /// </summary>
public static SmtpServerReply Ok => new SmtpServerReply(250, "OK");
#endregion
/// <summary>
#region Properties /// Gets the authorization required reply.
/// </summary>
/// <summary> public static SmtpServerReply AuthorizationRequired => new SmtpServerReply(530, "Authorization Required");
/// Gets the response severity.
/// </summary> #endregion
public SmtpReplyCodeSeverities ReplyCodeSeverity { get; }
#region Properties
/// <summary>
/// Gets the response category. /// <summary>
/// </summary> /// Gets the response severity.
public SmtpReplyCodeCategories ReplyCodeCategory { get; } /// </summary>
public SmtpReplyCodeSeverities ReplyCodeSeverity {
/// <summary> get;
/// Gets the numeric response code. }
/// </summary>
public int ReplyCode { get; } /// <summary>
/// Gets the response category.
/// <summary> /// </summary>
/// Gets the enhanced status code. public SmtpReplyCodeCategories ReplyCodeCategory {
/// </summary> get;
public string EnhancedStatusCode { get; } }
/// <summary> /// <summary>
/// Gets the content. /// Gets the numeric response code.
/// </summary> /// </summary>
public List<string> Content { get; } public Int32 ReplyCode {
get;
/// <summary> }
/// Returns true if the response code is between 200 and 599.
/// </summary> /// <summary>
public bool IsValid { get; } /// Gets the enhanced status code.
/// </summary>
/// <summary> public String EnhancedStatusCode {
/// Gets a value indicating whether this instance is positive. get;
/// </summary> }
public bool IsPositive => ReplyCode >= 200 && ReplyCode <= 399;
/// <summary>
#endregion /// Gets the content.
/// </summary>
#region Methods public List<String> Content {
get;
/// <summary> }
/// Parses the specified text into a Server Reply for thorough analysis.
/// </summary> /// <summary>
/// <param name="text">The text.</param> /// Returns true if the response code is between 200 and 599.
/// <returns>A new instance of SMTP server response object.</returns> /// </summary>
public static SmtpServerReply Parse(string text) public Boolean IsValid {
{ get;
var lines = text.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries); }
if (lines.Length == 0) return new SmtpServerReply();
/// <summary>
var lastLineParts = lines.Last().Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries); /// Gets a value indicating whether this instance is positive.
var enhancedStatusCode = string.Empty; /// </summary>
int.TryParse(lastLineParts[0], out var responseCode); public Boolean IsPositive => this.ReplyCode >= 200 && this.ReplyCode <= 399;
if (lastLineParts.Length > 1)
{ #endregion
if (lastLineParts[1].Split('.').Length == 3)
enhancedStatusCode = lastLineParts[1]; #region Methods
}
/// <summary>
var content = new List<string>(); /// Parses the specified text into a Server Reply for thorough analysis.
/// </summary>
for (var i = 0; i < lines.Length; i++) /// <param name="text">The text.</param>
{ /// <returns>A new instance of SMTP server response object.</returns>
var splitChar = i == lines.Length - 1 ? " " : "-"; public static SmtpServerReply Parse(String text) {
String[] lines = text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
var lineParts = lines[i].Split(new[] {splitChar}, 2, StringSplitOptions.None); if(lines.Length == 0) {
var lineContent = lineParts.Last(); return new SmtpServerReply();
if (string.IsNullOrWhiteSpace(enhancedStatusCode) == false) }
lineContent = lineContent.Replace(enhancedStatusCode, string.Empty).Trim();
String[] lastLineParts = lines.Last().Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
content.Add(lineContent); String enhancedStatusCode = String.Empty;
} _ = Int32.TryParse(lastLineParts[0], out Int32 responseCode);
if(lastLineParts.Length > 1) {
return new SmtpServerReply(responseCode, enhancedStatusCode, content.ToArray()); if(lastLineParts[1].Split('.').Length == 3) {
} enhancedStatusCode = lastLineParts[1];
}
/// <summary> }
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary> List<String> content = new List<String>();
/// <returns>
/// A <see cref="System.String" /> that represents this instance. for(Int32 i = 0; i < lines.Length; i++) {
/// </returns> String splitChar = i == lines.Length - 1 ? " " : "-";
public override string ToString()
{ String[] lineParts = lines[i].Split(new[] { splitChar }, 2, StringSplitOptions.None);
var responseCodeText = ReplyCode.ToString(CultureInfo.InvariantCulture); String lineContent = lineParts.Last();
var statusCodeText = string.IsNullOrWhiteSpace(EnhancedStatusCode) if(String.IsNullOrWhiteSpace(enhancedStatusCode) == false) {
? string.Empty lineContent = lineContent.Replace(enhancedStatusCode, String.Empty).Trim();
: $" {EnhancedStatusCode.Trim()}"; }
if (Content.Count == 0) return $"{responseCodeText}{statusCodeText}";
content.Add(lineContent);
var builder = new StringBuilder(); }
for (var i = 0; i < Content.Count; i++) return new SmtpServerReply(responseCode, enhancedStatusCode, content.ToArray());
{ }
var isLastLine = i == Content.Count - 1;
/// <summary>
builder.Append(isLastLine /// Returns a <see cref="System.String" /> that represents this instance.
? $"{responseCodeText}{statusCodeText} {Content[i]}" /// </summary>
: $"{responseCodeText}-{Content[i]}\r\n"); /// <returns>
} /// A <see cref="System.String" /> that represents this instance.
/// </returns>
return builder.ToString(); public override String ToString() {
} String responseCodeText = this.ReplyCode.ToString(CultureInfo.InvariantCulture);
String statusCodeText = String.IsNullOrWhiteSpace(this.EnhancedStatusCode)
#endregion ? String.Empty
} : $" {this.EnhancedStatusCode.Trim()}";
if(this.Content.Count == 0) {
return $"{responseCodeText}{statusCodeText}";
}
StringBuilder builder = new StringBuilder();
for(Int32 i = 0; i < this.Content.Count; i++) {
Boolean isLastLine = i == this.Content.Count - 1;
_ = builder.Append(isLastLine
? $"{responseCodeText}{statusCodeText} {this.Content[i]}"
: $"{responseCodeText}-{this.Content[i]}\r\n");
}
return builder.ToString();
}
#endregion
}
} }

View File

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

View File

@ -1,37 +1,34 @@
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,
}; };
/// <summary> /// <summary>
/// Discovers the specified SNMP time out. /// Discovers the specified SNMP time out.
/// </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);
@ -59,222 +56,212 @@
} }
}), }),
Task.Delay(snmpTimeOut), Task.Delay(snmpTimeOut),
}; };
Task.WaitAny(tasks); Task.WaitAny(tasks);
return endpoints.ToArray(); return endpoints.ToArray();
} }
/// <summary> /// <summary>
/// Gets the name of the public. /// Gets the name of the public.
/// </summary> /// </summary>
/// <param name="host">The host.</param> /// <param name="host">The host.</param>
/// <returns> /// <returns>
/// 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.
/// </summary> /// </summary>
/// <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> /// <returns>
/// 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
var commlength = Convert.ToInt16(response[6]); // If response, get the community name and MIB lengths
var miblength = Convert.ToInt16(response[23 + commlength]); Int16 commlength = Convert.ToInt16(response[6]);
Int16 miblength = Convert.ToInt16(response[23 + commlength]);
// Extract the MIB data from the SNMP response
var datalength = Convert.ToInt16(response[25 + commlength + miblength]); // Extract the MIB data from the SNMP response
var datastart = 26 + commlength + miblength; Int16 datalength = Convert.ToInt16(response[25 + 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--;
} }
return TimeSpan.FromSeconds(uptime); return TimeSpan.FromSeconds(uptime);
} }
/// <summary> /// <summary>
/// Gets the string. /// Gets the string.
/// </summary> /// </summary>
/// <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
var commlength = Convert.ToInt16(response[6]); // If response, get the community name and MIB lengths
var miblength = Convert.ToInt16(response[23 + commlength]); Int16 commlength = Convert.ToInt16(response[6]);
Int16 miblength = Convert.ToInt16(response[23 + commlength]);
// Extract the MIB data from the SNMP response
var datalength = Convert.ToInt16(response[25 + commlength + miblength]); // Extract the MIB data from the SNMP response
var datastart = 26 + commlength + miblength; Int16 datalength = Convert.ToInt16(response[25 + commlength + miblength]);
Int32 datastart = 26 + commlength + miblength;
return Encoding.ASCII.GetString(response, datastart, datalength);
} return Encoding.ASCII.GetString(response, datastart, datalength);
}
/// <summary>
/// Gets the specified host. /// <summary>
/// </summary> /// Gets the specified host.
/// <param name="host">The host.</param> /// </summary>
/// <param name="mibString">The mibString.</param> /// <param name="host">The host.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> /// <param name="mibString">The mibString.</param>
public static byte[] Get(IPEndPoint host, string mibString) => Get("get", host, "public", mibString); /// <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);
/// <summary>
/// Gets the specified request. /// <summary>
/// </summary> /// Gets the specified request.
/// <param name="request">The request.</param> /// </summary>
/// <param name="host">The host.</param> /// <param name="request">The request.</param>
/// <param name="community">The community.</param> /// <param name="host">The host.</param>
/// <param name="mibString">The mibString.</param> /// <param name="community">The community.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> /// <param name="mibString">The mibString.</param>
public static byte[] Get(string request, IPEndPoint host, string community, string mibString) /// <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) {
var packet = new byte[1024]; Byte[] packet = new Byte[1024];
var mib = new byte[1024]; Byte[] mib = new Byte[1024];
var comlen = community.Length; Int32 comlen = community.Length;
var mibvals = mibString.Split('.'); String[] mibvals = mibString.Split('.');
var miblen = mibvals.Length; Int32 miblen = mibvals.Length;
var cnt = 0; Int32 cnt = 0;
var orgmiblen = miblen; Int32 orgmiblen = miblen;
var pos = 0; Int32 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)); cnt += 2;
mib[cnt + 1] = Convert.ToByte(temp - ((temp / 128) * 128)); miblen++;
cnt += 2; } else {
miblen++; mib[cnt] = Convert.ToByte(temp);
} cnt++;
else }
{ }
mib[cnt] = Convert.ToByte(temp);
cnt++; Int32 snmplen = 29 + comlen + miblen - 1;
}
} // The SNMP sequence start
packet[pos++] = 0x30; // Sequence start
var snmplen = 29 + comlen + miblen - 1; packet[pos++] = Convert.ToByte(snmplen - 2); // sequence size
// The SNMP sequence start // SNMP version
packet[pos++] = 0x30; // Sequence start packet[pos++] = 0x02; // Integer type
packet[pos++] = Convert.ToByte(snmplen - 2); // sequence size packet[pos++] = 0x01; // length
packet[pos++] = 0x00; // SNMP version 1
// SNMP version
packet[pos++] = 0x02; // Integer type // Community name
packet[pos++] = 0x01; // length packet[pos++] = 0x04; // String type
packet[pos++] = 0x00; // SNMP version 1 packet[pos++] = Convert.ToByte(comlen); // length
// Community name // Convert community name to byte array
packet[pos++] = 0x04; // String type Byte[] data = Encoding.ASCII.GetBytes(community);
packet[pos++] = Convert.ToByte(comlen); // length
foreach(Byte t in data) {
// Convert community name to byte array packet[pos++] = t;
var data = Encoding.ASCII.GetBytes(community); }
foreach (var t in data) // Add GetRequest or GetNextRequest value
{ packet[pos++] = request == "get" ? (Byte)0xA0 : (Byte)0xA1;
packet[pos++] = t;
} packet[pos++] = Convert.ToByte(20 + miblen - 1); // Size of total MIB
// Add GetRequest or GetNextRequest value // Request ID
if (request == "get") packet[pos++] = 0x02; // Integer type
packet[pos++] = 0xA0; packet[pos++] = 0x04; // length
else packet[pos++] = 0x00; // SNMP request ID
packet[pos++] = 0xA1; packet[pos++] = 0x00;
packet[pos++] = 0x00;
packet[pos++] = Convert.ToByte(20 + miblen - 1); // Size of total MIB packet[pos++] = 0x01;
// Request ID // Error status
packet[pos++] = 0x02; // Integer type packet[pos++] = 0x02; // Integer type
packet[pos++] = 0x04; // length packet[pos++] = 0x01; // length
packet[pos++] = 0x00; // SNMP request ID packet[pos++] = 0x00; // SNMP error status
packet[pos++] = 0x00;
packet[pos++] = 0x00; // Error index
packet[pos++] = 0x01; packet[pos++] = 0x02; // Integer type
packet[pos++] = 0x01; // length
// Error status packet[pos++] = 0x00; // SNMP error index
packet[pos++] = 0x02; // Integer type
packet[pos++] = 0x01; // length // Start of variable bindings
packet[pos++] = 0x00; // SNMP error status packet[pos++] = 0x30; // Start of variable bindings sequence
// Error index packet[pos++] = Convert.ToByte(6 + miblen - 1); // Size of variable binding
packet[pos++] = 0x02; // Integer type
packet[pos++] = 0x01; // length packet[pos++] = 0x30; // Start of first variable bindings sequence
packet[pos++] = 0x00; // SNMP error index packet[pos++] = Convert.ToByte(6 + miblen - 1 - 2); // size
packet[pos++] = 0x06; // Object type
// Start of variable bindings packet[pos++] = Convert.ToByte(miblen - 1); // length
packet[pos++] = 0x30; // Start of variable bindings sequence
// Start of MIB
packet[pos++] = Convert.ToByte(6 + miblen - 1); // Size of variable binding packet[pos++] = 0x2b;
packet[pos++] = 0x30; // Start of first variable bindings sequence // Place MIB array in packet
packet[pos++] = Convert.ToByte(6 + miblen - 1 - 2); // size for(Int32 i = 2; i < miblen; i++) {
packet[pos++] = 0x06; // Object type packet[pos++] = Convert.ToByte(mib[i]);
packet[pos++] = Convert.ToByte(miblen - 1); // length }
// Start of MIB packet[pos++] = 0x05; // Null object value
packet[pos++] = 0x2b; packet[pos] = 0x00; // Null
// Place MIB array in packet // Send packet to destination
for (var i = 2; i < miblen; i++) SendPacket(host, packet, snmplen);
packet[pos++] = Convert.ToByte(mib[i]);
return packet;
packet[pos++] = 0x05; // Null object value }
packet[pos] = 0x00; // Null
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
// Send packet to destination private static void SendPacket(IPEndPoint host, Byte[] packet, Int32 length) {
SendPacket(host, packet, snmplen); Socket sock = new Socket(
AddressFamily.InterNetwork,
return packet; SocketType.Dgram,
} ProtocolType.Udp);
sock.SetSocketOption(
private static void SendPacket(IPEndPoint host, byte[] packet, int length) SocketOptionLevel.Socket,
{ SocketOptionName.ReceiveTimeout,
var sock = new Socket( 5000);
AddressFamily.InterNetwork, EndPoint ep = (EndPoint)host;
SocketType.Dgram, _ = sock.SendTo(packet, length, SocketFlags.None, host);
ProtocolType.Udp);
sock.SetSocketOption( // Receive response from packet
SocketOptionLevel.Socket, try {
SocketOptionName.ReceiveTimeout, _ = sock.ReceiveFrom(packet, ref ep);
5000); } catch(SocketException) {
var ep = (EndPoint) host; packet[0] = 0xff;
sock.SendTo(packet, length, SocketFlags.None, host); }
}
// Receive response from packet }
try
{
sock.ReceiveFrom(packet, ref ep);
}
catch (SocketException)
{
packet[0] = 0xff;
}
}
}
} }