Coding Styles
This commit is contained in:
parent
186792fde8
commit
c1e8637516
@ -1,214 +1,203 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
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>
|
||||
/// 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>
|
||||
/// <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>
|
||||
/// Initializes a new instance of the <see cref="AppWorkerBase"/> class.
|
||||
/// </summary>
|
||||
protected AppWorkerBase()
|
||||
{
|
||||
State = AppWorkerState.Stopped;
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [state changed].
|
||||
/// </summary>
|
||||
public event EventHandler<AppWorkerStateChangedEventArgs> StateChanged;
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the application service.
|
||||
/// In other words, useful to know whether the service is running.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The state.
|
||||
/// </value>
|
||||
public AppWorkerState State
|
||||
{
|
||||
get => _workerState;
|
||||
|
||||
private set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (value == _workerState) return;
|
||||
|
||||
$"Service state changing from {State} to {value}".Debug(GetType().Name);
|
||||
var newState = value;
|
||||
var oldState = _workerState;
|
||||
_workerState = value;
|
||||
|
||||
StateChanged?.Invoke(this, new AppWorkerStateChangedEventArgs(oldState, newState));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cancellation token.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The cancellation token.
|
||||
/// </value>
|
||||
public CancellationToken CancellationToken => _tokenSource?.Token ?? default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the thread is busy.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsBusy { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region AppWorkerBase Methods
|
||||
|
||||
/// <summary>
|
||||
/// Performs internal service initialization tasks required before starting the service.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Service cannot be initialized because it seems to be currently running.</exception>
|
||||
public virtual void Initialize() => CheckIsRunning();
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <exception cref="InvalidOperationException">Service cannot be started because it seems to be currently running.</exception>
|
||||
public virtual void Start()
|
||||
{
|
||||
CheckIsRunning();
|
||||
|
||||
CreateWorker();
|
||||
State = AppWorkerState.Running;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <exception cref="InvalidOperationException">Service cannot be stopped because it is not running.</exception>
|
||||
public virtual void Stop()
|
||||
{
|
||||
if (State != AppWorkerState.Running)
|
||||
return;
|
||||
|
||||
_tokenSource?.Cancel();
|
||||
"Service stop requested.".Debug(GetType().Name);
|
||||
State = AppWorkerState.Stopped;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => _tokenSource?.Dispose();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Abstract and Virtual Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called when an unhandled exception is thrown.
|
||||
/// </summary>
|
||||
/// <param name="ex">The ex.</param>
|
||||
protected virtual void OnWorkerThreadLoopException(Exception ex)
|
||||
=> "Service exception detected.".Debug(GetType().Name, ex);
|
||||
|
||||
/// <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
|
||||
}
|
||||
protected AppWorkerBase() {
|
||||
this.State = AppWorkerState.Stopped;
|
||||
this.IsBusy = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [state changed].
|
||||
/// </summary>
|
||||
public event EventHandler<AppWorkerStateChangedEventArgs> StateChanged;
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the application service.
|
||||
/// In other words, useful to know whether the service is running.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The state.
|
||||
/// </value>
|
||||
public AppWorkerState State {
|
||||
get => this._workerState;
|
||||
|
||||
private set {
|
||||
lock(this._syncLock) {
|
||||
if(value == this._workerState) {
|
||||
return;
|
||||
}
|
||||
|
||||
$"Service state changing from {this.State} to {value}".Debug(this.GetType().Name);
|
||||
AppWorkerState newState = value;
|
||||
AppWorkerState oldState = this._workerState;
|
||||
this._workerState = value;
|
||||
|
||||
StateChanged?.Invoke(this, new AppWorkerStateChangedEventArgs(oldState, newState));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cancellation token.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The cancellation token.
|
||||
/// </value>
|
||||
public CancellationToken CancellationToken => this._tokenSource?.Token ?? default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the thread is busy.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsBusy {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AppWorkerBase Methods
|
||||
|
||||
/// <summary>
|
||||
/// Performs internal service initialization tasks required before starting the service.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Service cannot be initialized because it seems to be currently running.</exception>
|
||||
public virtual void Initialize() => this.CheckIsRunning();
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <exception cref="InvalidOperationException">Service cannot be started because it seems to be currently running.</exception>
|
||||
public virtual void Start() {
|
||||
this.CheckIsRunning();
|
||||
|
||||
this.CreateWorker();
|
||||
this.State = AppWorkerState.Running;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <exception cref="InvalidOperationException">Service cannot be stopped because it is not running.</exception>
|
||||
public virtual void Stop() {
|
||||
if(this.State != AppWorkerState.Running) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._tokenSource?.Cancel();
|
||||
"Service stop requested.".Debug(this.GetType().Name);
|
||||
this.State = AppWorkerState.Stopped;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => this._tokenSource?.Dispose();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Abstract and Virtual Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called when an unhandled exception is thrown.
|
||||
/// </summary>
|
||||
/// <param name="ex">The ex.</param>
|
||||
protected virtual void OnWorkerThreadLoopException(Exception ex)
|
||||
=> "Service exception detected.".Debug(this.GetType().Name, ex);
|
||||
|
||||
/// <summary>
|
||||
/// This method is called when the user loop has exited.
|
||||
/// </summary>
|
||||
protected virtual void OnWorkerThreadExit() => "Service thread is stopping.".Debug(this.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(this.State != AppWorkerState.Stopped) {
|
||||
throw new InvalidOperationException("Service cannot be initialized because it seems to be currently running.");
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWorker() {
|
||||
this._tokenSource = new CancellationTokenSource();
|
||||
_ = this._tokenSource.Token.Register(() => {
|
||||
this.IsBusy = false;
|
||||
this.OnWorkerThreadExit();
|
||||
});
|
||||
|
||||
_ = Task.Run(async () => {
|
||||
this.IsBusy = true;
|
||||
|
||||
try {
|
||||
while(!this.CancellationToken.IsCancellationRequested) {
|
||||
await this.WorkerThreadLoop().ConfigureAwait(false);
|
||||
}
|
||||
} catch(AggregateException) {
|
||||
// Ignored
|
||||
} catch(Exception ex) {
|
||||
ex.Log(this.GetType().Name);
|
||||
this.OnWorkerThreadLoopException(ex);
|
||||
|
||||
if(!this._tokenSource.IsCancellationRequested) {
|
||||
this._tokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
},
|
||||
this._tokenSource.Token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// Represents event arguments whenever the state of an application service changes.
|
||||
/// Initializes a new instance of the <see cref="AppWorkerStateChangedEventArgs" /> class.
|
||||
/// </summary>
|
||||
public class AppWorkerStateChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AppWorkerStateChangedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="oldState">The old state.</param>
|
||||
/// <param name="newState">The new state.</param>
|
||||
public AppWorkerStateChangedEventArgs(AppWorkerState oldState, AppWorkerState newState)
|
||||
{
|
||||
OldState = oldState;
|
||||
NewState = newState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state to which the application service changed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The new state.
|
||||
/// </value>
|
||||
public AppWorkerState NewState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the old state.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The old state.
|
||||
/// </value>
|
||||
public AppWorkerState OldState { get; }
|
||||
}
|
||||
/// <param name="oldState">The old state.</param>
|
||||
/// <param name="newState">The new state.</param>
|
||||
public AppWorkerStateChangedEventArgs(AppWorkerState oldState, AppWorkerState newState) {
|
||||
this.OldState = oldState;
|
||||
this.NewState = newState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state to which the application service changed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The new state.
|
||||
/// </value>
|
||||
public AppWorkerState NewState {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the old state.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The old state.
|
||||
/// </value>
|
||||
public AppWorkerState OldState {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
/// 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.
|
||||
/// Initializes a new instance of the <see cref="CircularBuffer"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
public sealed class CircularBuffer : IDisposable
|
||||
{
|
||||
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)
|
||||
{
|
||||
/// <param name="bufferLength">Length of the buffer.</param>
|
||||
public CircularBuffer(Int32 bufferLength) {
|
||||
#if !NET452
|
||||
if (Runtime.OS != Swan.OperatingSystem.Windows)
|
||||
throw new InvalidOperationException("CircularBuffer component is only available in Windows");
|
||||
#endif
|
||||
|
||||
Length = bufferLength;
|
||||
_buffer = Marshal.AllocHGlobal(Length);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the capacity of this buffer.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The length.
|
||||
/// </value>
|
||||
public int Length { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current, 0-based read index.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The index of the read.
|
||||
/// </value>
|
||||
public int ReadIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current, 0-based write index.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The index of the write.
|
||||
/// </value>
|
||||
public int WriteIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an the object associated with the last write.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The write tag.
|
||||
/// </value>
|
||||
public TimeSpan WriteTag { get; private set; } = TimeSpan.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available bytes to read.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The readable count.
|
||||
/// </value>
|
||||
public int ReadableCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes that can be written.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The writable count.
|
||||
/// </value>
|
||||
public int WritableCount => Length - ReadableCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The capacity percent.
|
||||
/// </value>
|
||||
public double CapacityPercent => 1.0 * ReadableCount / Length;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes into the target array.
|
||||
/// </summary>
|
||||
/// <param name="requestedBytes">The requested bytes.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="targetOffset">The target offset.</param>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// Exception that is thrown when a method call is invalid for the object's current state.
|
||||
/// </exception>
|
||||
public void Read(int requestedBytes, byte[] target, int targetOffset)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (requestedBytes > ReadableCount)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to read {requestedBytes} bytes. Only {ReadableCount} bytes are available");
|
||||
}
|
||||
|
||||
var readCount = 0;
|
||||
while (readCount < requestedBytes)
|
||||
{
|
||||
var copyLength = Math.Min(Length - ReadIndex, requestedBytes - readCount);
|
||||
var sourcePtr = _buffer + ReadIndex;
|
||||
Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength);
|
||||
|
||||
readCount += copyLength;
|
||||
ReadIndex += copyLength;
|
||||
ReadableCount -= copyLength;
|
||||
|
||||
if (ReadIndex >= Length)
|
||||
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>
|
||||
/// <param name="writeTag">The write tag.</param>
|
||||
/// <exception cref="System.InvalidOperationException">Unable to write to circular buffer. Call the Read method to make some additional room.</exception>
|
||||
public void Write(IntPtr source, int length, TimeSpan writeTag)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (ReadableCount + length > 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)
|
||||
{
|
||||
var copyLength = Math.Min(Length - WriteIndex, length - writeCount);
|
||||
var sourcePtr = source + writeCount;
|
||||
var targetPtr = _buffer + WriteIndex;
|
||||
CopyMemory(targetPtr, sourcePtr, (uint) copyLength);
|
||||
|
||||
writeCount += copyLength;
|
||||
WriteIndex += copyLength;
|
||||
ReadableCount += copyLength;
|
||||
|
||||
if (WriteIndex >= Length)
|
||||
WriteIndex = 0;
|
||||
}
|
||||
|
||||
WriteTag = writeTag;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all states as if this buffer had just been created.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
WriteIndex = 0;
|
||||
ReadIndex = 0;
|
||||
WriteTag = TimeSpan.MinValue;
|
||||
ReadableCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_buffer == IntPtr.Zero) return;
|
||||
|
||||
Marshal.FreeHGlobal(_buffer);
|
||||
_buffer = IntPtr.Zero;
|
||||
Length = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fast pointer memory block copy function.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination.</param>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
[DllImport("kernel32")]
|
||||
public static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
this.Length = bufferLength;
|
||||
this._buffer = Marshal.AllocHGlobal(this.Length);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the capacity of this buffer.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The length.
|
||||
/// </value>
|
||||
public Int32 Length {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current, 0-based read index.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The index of the read.
|
||||
/// </value>
|
||||
public Int32 ReadIndex {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current, 0-based write index.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The index of the write.
|
||||
/// </value>
|
||||
public Int32 WriteIndex {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an the object associated with the last write.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The write tag.
|
||||
/// </value>
|
||||
public TimeSpan WriteTag { get; private set; } = TimeSpan.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available bytes to read.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The readable count.
|
||||
/// </value>
|
||||
public Int32 ReadableCount {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes that can be written.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The writable count.
|
||||
/// </value>
|
||||
public Int32 WritableCount => this.Length - this.ReadableCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The capacity percent.
|
||||
/// </value>
|
||||
public Double CapacityPercent => 1.0 * this.ReadableCount / this.Length;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes into the target array.
|
||||
/// </summary>
|
||||
/// <param name="requestedBytes">The requested bytes.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="targetOffset">The target offset.</param>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// Exception that is thrown when a method call is invalid for the object's current state.
|
||||
/// </exception>
|
||||
public void Read(Int32 requestedBytes, Byte[] target, Int32 targetOffset) {
|
||||
lock(this._syncLock) {
|
||||
if(requestedBytes > this.ReadableCount) {
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to read {requestedBytes} bytes. Only {this.ReadableCount} bytes are available");
|
||||
}
|
||||
|
||||
Int32 readCount = 0;
|
||||
while(readCount < requestedBytes) {
|
||||
Int32 copyLength = Math.Min(this.Length - this.ReadIndex, requestedBytes - readCount);
|
||||
IntPtr sourcePtr = this._buffer + this.ReadIndex;
|
||||
Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength);
|
||||
|
||||
readCount += copyLength;
|
||||
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>
|
||||
/// <param name="writeTag">The write tag.</param>
|
||||
/// <exception cref="System.InvalidOperationException">Unable to write to circular buffer. Call the Read method to make some additional room.</exception>
|
||||
public void Write(IntPtr source, Int32 length, TimeSpan writeTag) {
|
||||
lock(this._syncLock) {
|
||||
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.");
|
||||
}
|
||||
|
||||
Int32 writeCount = 0;
|
||||
while(writeCount < length) {
|
||||
Int32 copyLength = Math.Min(this.Length - this.WriteIndex, length - writeCount);
|
||||
IntPtr sourcePtr = source + writeCount;
|
||||
IntPtr targetPtr = this._buffer + this.WriteIndex;
|
||||
CopyMemory(targetPtr, sourcePtr, (UInt32)copyLength);
|
||||
|
||||
writeCount += copyLength;
|
||||
this.WriteIndex += copyLength;
|
||||
this.ReadableCount += copyLength;
|
||||
|
||||
if(this.WriteIndex >= this.Length) {
|
||||
this.WriteIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.WriteTag = writeTag;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all states as if this buffer had just been created.
|
||||
/// </summary>
|
||||
public void Clear() {
|
||||
lock(this._syncLock) {
|
||||
this.WriteIndex = 0;
|
||||
this.ReadIndex = 0;
|
||||
this.WriteTag = TimeSpan.MinValue;
|
||||
this.ReadableCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(this._buffer == IntPtr.Zero) {
|
||||
return;
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(this._buffer);
|
||||
this._buffer = IntPtr.Zero;
|
||||
this.Length = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fast pointer memory block copy function.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination.</param>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
[DllImport("kernel32")]
|
||||
public static extern void CopyMemory(IntPtr destination, IntPtr source, UInt32 length);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,109 +1,101 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
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>
|
||||
/// Represents a CsProjFile (and FsProjFile) parser.
|
||||
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
|
||||
/// </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 bool _leaveOpen;
|
||||
private readonly XDocument _xmlDocument;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
public CsProjFile(string filename = null)
|
||||
: this(OpenFile(filename))
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
|
||||
/// <exception cref="ArgumentException">Project file is not of the new .csproj type.</exception>
|
||||
public CsProjFile(Stream stream, bool leaveOpen = false)
|
||||
{
|
||||
_stream = stream;
|
||||
_leaveOpen = leaveOpen;
|
||||
|
||||
_xmlDocument = XDocument.Load(stream);
|
||||
|
||||
var projectElement = _xmlDocument.Descendants("Project").FirstOrDefault();
|
||||
var sdkAttribute = projectElement?.Attribute("Sdk");
|
||||
var 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.");
|
||||
}
|
||||
|
||||
Metadata = Activator.CreateInstance<T>();
|
||||
Metadata.SetData(_xmlDocument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the metadata.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The nu get metadata.
|
||||
/// </value>
|
||||
public T Metadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Saves this instance.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
_stream.SetLength(0);
|
||||
_stream.Position = 0;
|
||||
|
||||
_xmlDocument.Save(_stream);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_leaveOpen)
|
||||
{
|
||||
_stream?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
/// <param name="filename">The filename.</param>
|
||||
public CsProjFile(String filename = null)
|
||||
: this(OpenFile(filename)) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
|
||||
/// <exception cref="ArgumentException">Project file is not of the new .csproj type.</exception>
|
||||
public CsProjFile(Stream stream, Boolean leaveOpen = false) {
|
||||
this._stream = stream;
|
||||
this._leaveOpen = leaveOpen;
|
||||
|
||||
this._xmlDocument = XDocument.Load(stream);
|
||||
|
||||
XElement projectElement = this._xmlDocument.Descendants("Project").FirstOrDefault();
|
||||
XAttribute sdkAttribute = projectElement?.Attribute("Sdk");
|
||||
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.");
|
||||
}
|
||||
|
||||
this.Metadata = Activator.CreateInstance<T>();
|
||||
this.Metadata.SetData(this._xmlDocument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the metadata.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The nu get metadata.
|
||||
/// </value>
|
||||
public T Metadata {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves this instance.
|
||||
/// </summary>
|
||||
public void Save() {
|
||||
this._stream.SetLength(0);
|
||||
this._stream.Position = 0;
|
||||
|
||||
this._xmlDocument.Save(this._stream);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(!this._leaveOpen) {
|
||||
this._stream?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// Represents a CsProj metadata abstract class
|
||||
/// to use with <c>CsProjFile</c> parser.
|
||||
@ -17,7 +18,7 @@
|
||||
/// <value>
|
||||
/// The package identifier.
|
||||
/// </value>
|
||||
public string PackageId => FindElement(nameof(PackageId))?.Value;
|
||||
public String PackageId => this.FindElement(nameof(this.PackageId))?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the assembly.
|
||||
@ -25,7 +26,7 @@
|
||||
/// <value>
|
||||
/// The name of the assembly.
|
||||
/// </value>
|
||||
public string AssemblyName => FindElement(nameof(AssemblyName))?.Value;
|
||||
public String AssemblyName => this.FindElement(nameof(this.AssemblyName))?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target frameworks.
|
||||
@ -33,7 +34,7 @@
|
||||
/// <value>
|
||||
/// The target frameworks.
|
||||
/// </value>
|
||||
public string TargetFrameworks => FindElement(nameof(TargetFrameworks))?.Value;
|
||||
public String TargetFrameworks => this.FindElement(nameof(this.TargetFrameworks))?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target framework.
|
||||
@ -41,7 +42,7 @@
|
||||
/// <value>
|
||||
/// The target framework.
|
||||
/// </value>
|
||||
public string TargetFramework => FindElement(nameof(TargetFramework))?.Value;
|
||||
public String TargetFramework => this.FindElement(nameof(this.TargetFramework))?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version.
|
||||
@ -49,25 +50,25 @@
|
||||
/// <value>
|
||||
/// The version.
|
||||
/// </value>
|
||||
public string Version => FindElement(nameof(Version))?.Value;
|
||||
public String Version => this.FindElement(nameof(this.Version))?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Parses the cs proj tags.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments.</param>
|
||||
public abstract void ParseCsProjTags(ref string[] args);
|
||||
public abstract void ParseCsProjTags(ref String[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the data.
|
||||
/// </summary>
|
||||
/// <param name="xmlDocument">The XML document.</param>
|
||||
public void SetData(XDocument xmlDocument) => _xmlDocument = xmlDocument;
|
||||
public void SetData(XDocument xmlDocument) => this._xmlDocument = xmlDocument;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the element.
|
||||
/// </summary>
|
||||
/// <param name="elementName">Name of the element.</param>
|
||||
/// <returns>A XElement.</returns>
|
||||
protected XElement FindElement(string elementName) => _xmlDocument.Descendants(elementName).FirstOrDefault();
|
||||
protected XElement FindElement(String elementName) => this._xmlDocument.Descendants(elementName).FirstOrDefault();
|
||||
}
|
||||
}
|
@ -1,144 +1,139 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Abstractions;
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Unosquare.Swan.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>
|
||||
/// Represents logic providing several delay mechanisms.
|
||||
/// Initializes a new instance of the <see cref="DelayProvider"/> class.
|
||||
/// </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 bool _isDisposed;
|
||||
private IWaitEvent _delayEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DelayProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="strategy">The strategy.</param>
|
||||
public DelayProvider(DelayStrategy strategy = DelayStrategy.TaskDelay)
|
||||
{
|
||||
Strategy = strategy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different ways of providing delays.
|
||||
/// </summary>
|
||||
public enum DelayStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Using the Thread.Sleep(15) mechanism.
|
||||
/// </summary>
|
||||
ThreadSleep,
|
||||
|
||||
/// <summary>
|
||||
/// Using the Task.Delay(1).Wait mechanism.
|
||||
/// </summary>
|
||||
TaskDelay,
|
||||
|
||||
/// <summary>
|
||||
/// Using a wait event that completes in a background ThreadPool thread.
|
||||
/// </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;
|
||||
/// <param name="strategy">The strategy.</param>
|
||||
public DelayProvider(DelayStrategy strategy = DelayStrategy.TaskDelay) => this.Strategy = strategy;
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different ways of providing delays.
|
||||
/// </summary>
|
||||
public enum DelayStrategy {
|
||||
/// <summary>
|
||||
/// Using the Thread.Sleep(15) mechanism.
|
||||
/// </summary>
|
||||
ThreadSleep,
|
||||
|
||||
/// <summary>
|
||||
/// Using the Task.Delay(1).Wait mechanism.
|
||||
/// </summary>
|
||||
TaskDelay,
|
||||
|
||||
/// <summary>
|
||||
/// Using a wait event that completes in a background ThreadPool thread.
|
||||
/// </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(this._syncRoot) {
|
||||
if(this._isDisposed) {
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
this._delayStopwatch.Restart();
|
||||
|
||||
switch(this.Strategy) {
|
||||
case DelayStrategy.ThreadSleep:
|
||||
DelaySleep();
|
||||
break;
|
||||
case DelayStrategy.TaskDelay:
|
||||
DelayTask();
|
||||
break;
|
||||
#if !NETSTANDARD1_3
|
||||
case DelayStrategy.ThreadPool:
|
||||
this.DelayThreadPool();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
return _delayStopwatch.Elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
#region Dispose Pattern
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
_delayEvent?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Delay Mechanisms
|
||||
|
||||
private static void DelaySleep() => Thread.Sleep(15);
|
||||
|
||||
private static void DelayTask() => Task.Delay(1).Wait();
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
private void DelayThreadPool()
|
||||
{
|
||||
if (_delayEvent == null)
|
||||
_delayEvent = WaitEventFactory.Create(isCompleted: true, useSlim: true);
|
||||
|
||||
_delayEvent.Begin();
|
||||
ThreadPool.QueueUserWorkItem((s) =>
|
||||
{
|
||||
DelaySleep();
|
||||
_delayEvent.Complete();
|
||||
});
|
||||
|
||||
_delayEvent.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
return this._delayStopwatch.Elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
#region Dispose Pattern
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
lock(this._syncRoot) {
|
||||
if(this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
this._delayEvent?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Delay Mechanisms
|
||||
|
||||
private static void DelaySleep() => Thread.Sleep(15);
|
||||
|
||||
private static void DelayTask() => Task.Delay(1).Wait();
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
private void DelayThreadPool() {
|
||||
if(this._delayEvent == null) {
|
||||
this._delayEvent = WaitEventFactory.Create(isCompleted: true, useSlim: true);
|
||||
}
|
||||
|
||||
this._delayEvent.Begin();
|
||||
_ = ThreadPool.QueueUserWorkItem((s) => {
|
||||
DelaySleep();
|
||||
this._delayEvent.Complete();
|
||||
});
|
||||
|
||||
this._delayEvent.Wait();
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,114 +1,113 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Resolution settings.
|
||||
/// </summary>
|
||||
public class DependencyContainerResolveOptions {
|
||||
/// <summary>
|
||||
/// Resolution settings.
|
||||
/// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found).
|
||||
/// </summary>
|
||||
public class 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,
|
||||
};
|
||||
}
|
||||
|
||||
public static DependencyContainerResolveOptions Default { get; } = new DependencyContainerResolveOptions();
|
||||
|
||||
/// <summary>
|
||||
/// Defines Resolution actions.
|
||||
/// Gets or sets the unregistered resolution action.
|
||||
/// </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,
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The unregistered resolution action.
|
||||
/// </value>
|
||||
public DependencyContainerUnregisteredResolutionActions UnregisteredResolutionAction {
|
||||
get; set;
|
||||
} =
|
||||
DependencyContainerUnregisteredResolutionActions.AttemptResolve;
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates failure actions.
|
||||
/// Gets or sets the named resolution failure action.
|
||||
/// </summary>
|
||||
public enum DependencyContainerNamedResolutionFailureActions
|
||||
{
|
||||
/// <summary>
|
||||
/// The attempt unnamed resolution
|
||||
/// </summary>
|
||||
AttemptUnnamedResolution,
|
||||
|
||||
/// <summary>
|
||||
/// The fail
|
||||
/// </summary>
|
||||
Fail,
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The named resolution failure action.
|
||||
/// </value>
|
||||
public DependencyContainerNamedResolutionFailureActions NamedResolutionFailureAction {
|
||||
get; set;
|
||||
} =
|
||||
DependencyContainerNamedResolutionFailureActions.Fail;
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates duplicate definition actions.
|
||||
/// Gets the constructor parameters.
|
||||
/// </summary>
|
||||
public enum DependencyContainerDuplicateImplementationActions
|
||||
{
|
||||
/// <summary>
|
||||
/// The register single
|
||||
/// </summary>
|
||||
RegisterSingle,
|
||||
|
||||
/// <summary>
|
||||
/// The register multiple
|
||||
/// </summary>
|
||||
RegisterMultiple,
|
||||
|
||||
/// <summary>
|
||||
/// The fail
|
||||
/// </summary>
|
||||
Fail,
|
||||
}
|
||||
/// <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>
|
||||
/// 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,
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// A Message to be published/delivered by Messenger.
|
||||
/// The sender of the message, or null if not supported by the message implementation.
|
||||
/// </summary>
|
||||
public interface IMessageHubMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The sender of the message, or null if not supported by the message implementation.
|
||||
/// </summary>
|
||||
object Sender { get; }
|
||||
}
|
||||
Object Sender {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,466 +1,443 @@
|
||||
// ===============================================================================
|
||||
// TinyIoC - TinyMessenger
|
||||
//
|
||||
// A simple messenger/event aggregator.
|
||||
//
|
||||
// https://github.com/grumpydev/TinyIoC/blob/master/src/TinyIoC/TinyMessenger.cs
|
||||
// ===============================================================================
|
||||
// Copyright © Steven Robbins. All rights reserved.
|
||||
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
|
||||
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
|
||||
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// ===============================================================================
|
||||
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
#region Message Types / Interfaces
|
||||
|
||||
// ===============================================================================
|
||||
// TinyIoC - TinyMessenger
|
||||
//
|
||||
// A simple messenger/event aggregator.
|
||||
//
|
||||
// https://github.com/grumpydev/TinyIoC/blob/master/src/TinyIoC/TinyMessenger.cs
|
||||
// ===============================================================================
|
||||
// Copyright © Steven Robbins. All rights reserved.
|
||||
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
|
||||
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
|
||||
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// ===============================================================================
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
#region Message Types / Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message subscription.
|
||||
/// </summary>
|
||||
public interface IMessageHubSubscription {
|
||||
/// <summary>
|
||||
/// Represents a message subscription.
|
||||
/// Token returned to the subscribed to reference this subscription.
|
||||
/// </summary>
|
||||
public interface IMessageHubSubscription
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
|
||||
MessageHubSubscriptionToken SubscriptionToken {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// marshal delivery actions onto a particular thread.
|
||||
/// All messages of this type will be delivered.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
MessageHubSubscriptionToken Subscribe<TMessage>(
|
||||
Action<TMessage> deliveryAction,
|
||||
Boolean useStrongReferences,
|
||||
IMessageHubProxy proxy)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
|
||||
/// <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>
|
||||
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
|
||||
|
||||
/// <typeparam name="TMessage">Type of message.</typeparam>
|
||||
/// <param name="subscriptionToken">Subscription token received from Subscribe.</param>
|
||||
void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Messenger hub responsible for taking subscriptions/publications and delivering of messages.
|
||||
/// Publish a message to any subscribers.
|
||||
/// </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.
|
||||
///
|
||||
/// 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>
|
||||
MessageHubSubscriptionToken Subscribe<TMessage>(
|
||||
Action<TMessage> deliveryAction,
|
||||
bool useStrongReferences,
|
||||
IMessageHubProxy proxy)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
|
||||
/// <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>
|
||||
MessageHubSubscriptionToken Subscribe<TMessage>(
|
||||
Action<TMessage> deliveryAction,
|
||||
Func<TMessage, bool> messageFilter,
|
||||
bool useStrongReferences,
|
||||
IMessageHubProxy proxy)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from a particular message type.
|
||||
///
|
||||
/// Does not throw an exception if the subscription is not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">Type of message.</typeparam>
|
||||
/// <param name="subscriptionToken">Subscription token received from Subscribe.</param>
|
||||
void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Publish a message to any subscribers.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">Type of message.</typeparam>
|
||||
/// <param name="message">Message to deliver.</param>
|
||||
void Publish<TMessage>(TMessage message)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
|
||||
/// <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 from Publish action.</returns>
|
||||
Task PublishAsync<TMessage>(TMessage message)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
}
|
||||
|
||||
/// <typeparam name="TMessage">Type of message.</typeparam>
|
||||
/// <param name="message">Message to deliver.</param>
|
||||
void Publish<TMessage>(TMessage message)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
|
||||
/// <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 from Publish action.</returns>
|
||||
Task PublishAsync<TMessage>(TMessage message)
|
||||
where TMessage : class, IMessageHubMessage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hub Implementation
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <example>
|
||||
/// The following code describes how to use a MessageHub. Both the
|
||||
/// subscription and the message sending are done in the same place but this is only for explanatory purposes.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // using DependencyContainer to create an instance of MessageHub
|
||||
/// var messageHub = DependencyContainer
|
||||
/// .Current
|
||||
/// .Resolve<IMessageHub>() as MessageHub;
|
||||
///
|
||||
/// // create an instance of the publisher class
|
||||
/// // which has a string as its content
|
||||
/// var message = new MessageHubGenericMessage<string>(new object(), "SWAN");
|
||||
///
|
||||
/// // subscribe to the publisher's event
|
||||
/// // and just print out the content which is a string
|
||||
/// // a token is returned which can be used to unsubscribe later on
|
||||
/// var token = messageHub
|
||||
/// .Subscribe<MessageHubGenericMessage<string>>(m => m.Content.Info());
|
||||
///
|
||||
/// // publish the message described above which is
|
||||
/// // the string 'SWAN'
|
||||
/// messageHub.Publish(message);
|
||||
///
|
||||
/// // unsuscribe, we will no longer receive any messages
|
||||
/// messageHub.Unsubscribe<MessageHubGenericMessage<string>>(token);
|
||||
///
|
||||
/// Terminal.Flush();
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public sealed class MessageHub : IMessageHub {
|
||||
#region Private Types and Interfaces
|
||||
|
||||
private readonly Object _subscriptionsPadlock = new Object();
|
||||
|
||||
private readonly Dictionary<Type, List<SubscriptionItem>> _subscriptions =
|
||||
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
|
||||
|
||||
#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 />
|
||||
/// <example>
|
||||
/// The following code describes how to use a MessageHub. Both the
|
||||
/// subscription and the message sending are done in the same place but this is only for explanatory purposes.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // using DependencyContainer to create an instance of MessageHub
|
||||
/// var messageHub = DependencyContainer
|
||||
/// .Current
|
||||
/// .Resolve<IMessageHub>() as MessageHub;
|
||||
///
|
||||
/// // create an instance of the publisher class
|
||||
/// // which has a string as its content
|
||||
/// var message = new MessageHubGenericMessage<string>(new object(), "SWAN");
|
||||
///
|
||||
/// // subscribe to the publisher's event
|
||||
/// // and just print out the content which is a string
|
||||
/// // a token is returned which can be used to unsubscribe later on
|
||||
/// var token = messageHub
|
||||
/// .Subscribe<MessageHubGenericMessage<string>>(m => m.Content.Info());
|
||||
///
|
||||
/// // publish the message described above which is
|
||||
/// // the string 'SWAN'
|
||||
/// messageHub.Publish(message);
|
||||
///
|
||||
/// // unsuscribe, we will no longer receive any messages
|
||||
/// messageHub.Unsubscribe<MessageHubGenericMessage<string>>(token);
|
||||
///
|
||||
/// Terminal.Flush();
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public sealed class MessageHub : IMessageHub
|
||||
{
|
||||
#region Private Types and Interfaces
|
||||
|
||||
private readonly object _subscriptionsPadlock = new object();
|
||||
|
||||
private readonly Dictionary<Type, List<SubscriptionItem>> _subscriptions =
|
||||
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, 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
|
||||
}
|
||||
|
||||
public void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
|
||||
where TMessage : class, IMessageHubMessage {
|
||||
if(subscriptionToken == null) {
|
||||
throw new ArgumentNullException(nameof(subscriptionToken));
|
||||
}
|
||||
|
||||
lock(this._subscriptionsPadlock) {
|
||||
if(!this._subscriptions.TryGetValue(typeof(TMessage), out List<SubscriptionItem> currentSubscriptions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SubscriptionItem> 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(this._subscriptionsPadlock) {
|
||||
if(!this._subscriptions.TryGetValue(typeof(TMessage), out List<SubscriptionItem> 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 => Task.Run(() => this.Publish(message));
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
@ -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>
|
||||
/// 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>
|
||||
public abstract class MessageHubMessageBase
|
||||
: 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;
|
||||
}
|
||||
|
||||
private readonly WeakReference _sender;
|
||||
|
||||
/// <summary>
|
||||
/// Generic message with user specified content.
|
||||
/// Initializes a new instance of the <see cref="MessageHubMessageBase"/> class.
|
||||
/// </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)
|
||||
{
|
||||
Content = content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contents of the message.
|
||||
/// </summary>
|
||||
public TContent Content { get; protected set; }
|
||||
}
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <exception cref="System.ArgumentNullException">sender.</exception>
|
||||
protected MessageHubMessageBase(Object sender) {
|
||||
if(sender == null) {
|
||||
throw new ArgumentNullException(nameof(sender));
|
||||
}
|
||||
|
||||
this._sender = new WeakReference(sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The sender of the message, or null if not supported by the message implementation.
|
||||
/// </summary>
|
||||
public Object Sender => this._sender?.Target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic message with user specified content.
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +1,49 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System;
|
||||
#if NETSTANDARD1_3
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
#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>
|
||||
/// Represents an active subscription to a message.
|
||||
/// Initializes a new instance of the <see cref="MessageHubSubscriptionToken"/> class.
|
||||
/// </summary>
|
||||
public sealed class MessageHubSubscriptionToken
|
||||
: IDisposable
|
||||
{
|
||||
private readonly WeakReference _hub;
|
||||
private readonly Type _messageType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageHubSubscriptionToken"/> class.
|
||||
/// </summary>
|
||||
/// <param name="hub">The hub.</param>
|
||||
/// <param name="messageType">Type of the message.</param>
|
||||
/// <exception cref="System.ArgumentNullException">hub.</exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">messageType.</exception>
|
||||
public MessageHubSubscriptionToken(IMessageHub hub, Type messageType)
|
||||
{
|
||||
if (hub == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hub));
|
||||
}
|
||||
|
||||
if (!typeof(IMessageHubMessage).IsAssignableFrom(messageType))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(messageType));
|
||||
}
|
||||
|
||||
_hub = new WeakReference(hub);
|
||||
_messageType = messageType;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
/// <param name="hub">The hub.</param>
|
||||
/// <param name="messageType">Type of the message.</param>
|
||||
/// <exception cref="System.ArgumentNullException">hub.</exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">messageType.</exception>
|
||||
public MessageHubSubscriptionToken(IMessageHub hub, Type messageType) {
|
||||
if(hub == null) {
|
||||
throw new ArgumentNullException(nameof(hub));
|
||||
}
|
||||
|
||||
if(!typeof(IMessageHubMessage).IsAssignableFrom(messageType)) {
|
||||
throw new ArgumentOutOfRangeException(nameof(messageType));
|
||||
}
|
||||
|
||||
this._hub = new WeakReference(hub);
|
||||
this._messageType = messageType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(this._hub.IsAlive && this._hub.Target is IMessageHub hub) {
|
||||
System.Reflection.MethodInfo unsubscribeMethod = typeof(IMessageHub).GetMethod(nameof(IMessageHub.Unsubscribe),
|
||||
new[] { typeof(MessageHubSubscriptionToken) });
|
||||
unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(this._messageType);
|
||||
_ = unsubscribeMethod.Invoke(hub, new Object[] { this });
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,424 +1,390 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Exceptions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Unosquare.Swan.Exceptions;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
/// <summary>
|
||||
/// Represents an abstract class for Object Factory.
|
||||
/// </summary>
|
||||
public abstract class ObjectFactoryBase {
|
||||
/// <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>
|
||||
public abstract class ObjectFactoryBase
|
||||
{
|
||||
/// <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 />
|
||||
public virtual Boolean AssumeConstruction => false;
|
||||
|
||||
/// <summary>
|
||||
/// IObjectFactory that creates new instances of types for each resolution.
|
||||
/// The type the factory instantiates.
|
||||
/// </summary>
|
||||
internal class MultiInstanceFactory : ObjectFactoryBase
|
||||
{
|
||||
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 />
|
||||
public abstract Type CreatesType {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IObjectFactory that invokes a specified delegate to construct the object.
|
||||
/// Constructor to use, if specified.
|
||||
/// </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)
|
||||
{
|
||||
_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 />
|
||||
public ConstructorInfo Constructor {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IObjectFactory that invokes a specified delegate to construct the object
|
||||
/// Holds the delegate using a weak reference.
|
||||
/// Gets the singleton variant.
|
||||
/// </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));
|
||||
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The singleton variant.
|
||||
/// </value>
|
||||
/// <exception cref="DependencyContainerRegistrationException">singleton.</exception>
|
||||
public virtual ObjectFactoryBase SingletonVariant =>
|
||||
throw new DependencyContainerRegistrationException(this.GetType(), "singleton");
|
||||
|
||||
/// <summary>
|
||||
/// Stores an particular instance to return for a type.
|
||||
/// Gets the multi instance variant.
|
||||
/// </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);
|
||||
|
||||
_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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The multi instance variant.
|
||||
/// </value>
|
||||
/// <exception cref="DependencyContainerRegistrationException">multi-instance.</exception>
|
||||
public virtual ObjectFactoryBase MultiInstanceVariant =>
|
||||
throw new DependencyContainerRegistrationException(this.GetType(), "multi-instance");
|
||||
|
||||
/// <summary>
|
||||
/// Stores the instance with a weak reference.
|
||||
/// Gets the strong reference variant.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
_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();
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The strong reference variant.
|
||||
/// </value>
|
||||
/// <exception cref="DependencyContainerRegistrationException">strong reference.</exception>
|
||||
public virtual ObjectFactoryBase StrongReferenceVariant =>
|
||||
throw new DependencyContainerRegistrationException(this.GetType(), "strong reference");
|
||||
|
||||
/// <summary>
|
||||
/// A factory that lazy instantiates a type and always returns the same instance.
|
||||
/// Gets the weak reference variant.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
_registerType = registerType;
|
||||
_registerImplementation = registerImplementation;
|
||||
}
|
||||
|
||||
public override Type CreatesType => _registerImplementation;
|
||||
|
||||
public override ObjectFactoryBase SingletonVariant => this;
|
||||
|
||||
public override ObjectFactoryBase MultiInstanceVariant =>
|
||||
new MultiInstanceFactory(_registerType, _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 (_singletonLock)
|
||||
{
|
||||
if (_current == null)
|
||||
_current = container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options);
|
||||
}
|
||||
|
||||
return _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.
|
||||
GetObject(type, parent, DependencyContainerResolveOptions.Default);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Dispose() => (_current as IDisposable)?.Dispose();
|
||||
}
|
||||
/// <value>
|
||||
/// The weak reference variant.
|
||||
/// </value>
|
||||
/// <exception cref="DependencyContainerRegistrationException">weak reference.</exception>
|
||||
public virtual ObjectFactoryBase WeakReferenceVariant =>
|
||||
throw new DependencyContainerRegistrationException(this.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) => this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// IObjectFactory that creates new instances of types for each resolution.
|
||||
/// </summary>
|
||||
internal class MultiInstanceFactory : ObjectFactoryBase {
|
||||
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);
|
||||
}
|
||||
|
||||
this._registerType = registerType;
|
||||
this._registerImplementation = registerImplementation;
|
||||
}
|
||||
|
||||
public override Type CreatesType => this._registerImplementation;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
/// Represents the text of the standard output and standard error
|
||||
/// of a process, including its exit code.
|
||||
/// Initializes a new instance of the <see cref="ProcessResult" /> class.
|
||||
/// </summary>
|
||||
public class ProcessResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProcessResult" /> class.
|
||||
/// </summary>
|
||||
/// <param name="exitCode">The exit code.</param>
|
||||
/// <param name="standardOutput">The standard output.</param>
|
||||
/// <param name="standardError">The standard error.</param>
|
||||
public ProcessResult(int exitCode, string standardOutput, string standardError)
|
||||
{
|
||||
ExitCode = exitCode;
|
||||
StandardOutput = standardOutput;
|
||||
StandardError = standardError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exit code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The exit code.
|
||||
/// </value>
|
||||
public int ExitCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text of the standard output.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The standard output.
|
||||
/// </value>
|
||||
public string StandardOutput { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text of the standard error.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The standard error.
|
||||
/// </value>
|
||||
public string StandardError { get; }
|
||||
}
|
||||
/// <param name="exitCode">The exit code.</param>
|
||||
/// <param name="standardOutput">The standard output.</param>
|
||||
/// <param name="standardError">The standard error.</param>
|
||||
public ProcessResult(Int32 exitCode, String standardOutput, String standardError) {
|
||||
this.ExitCode = exitCode;
|
||||
this.StandardOutput = standardOutput;
|
||||
this.StandardError = standardError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exit code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The exit code.
|
||||
/// </value>
|
||||
public Int32 ExitCode {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text of the standard output.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The standard output.
|
||||
/// </value>
|
||||
public String StandardOutput {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text of the standard error.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The standard error.
|
||||
/// </value>
|
||||
public String StandardError {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,474 +1,447 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
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>
|
||||
/// Provides methods to help create external processes, and efficiently capture the
|
||||
/// standard error and standard output streams.
|
||||
/// Defines a delegate to handle binary data reception from the standard
|
||||
/// output or standard error streams from a process.
|
||||
/// </summary>
|
||||
public static class ProcessRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a delegate to handle binary data reception from the standard
|
||||
/// output or standard error streams from a process.
|
||||
/// </summary>
|
||||
/// <param name="processData">The process data.</param>
|
||||
/// <param name="process">The process.</param>
|
||||
public delegate void ProcessDataReceivedCallback(byte[] processData, Process process);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the process asynchronously and if the exit code is 0,
|
||||
/// returns all of the standard output text. If the exit code is something other than 0
|
||||
/// it returns the contents of standard error.
|
||||
/// This method is meant to be used for programs that output a relatively small amount of text.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The type of the result produced by this Task.</returns>
|
||||
public static Task<string> GetProcessOutputAsync(string filename, CancellationToken ct = default) =>
|
||||
GetProcessOutputAsync(filename, string.Empty, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the process asynchronously and if the exit code is 0,
|
||||
/// returns all of the standard output text. If the exit code is something other than 0
|
||||
/// it returns the contents of standard error.
|
||||
/// This method is meant to be used for programs that output a relatively small amount of text.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The type of the result produced by this Task.</returns>
|
||||
/// <example>
|
||||
/// The following code explains how to run an external process using the
|
||||
/// <see cref="GetProcessOutputAsync(string, string, CancellationToken)"/> method.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using System.Threading.Tasks;
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// static async Task Main()
|
||||
/// {
|
||||
/// // execute a process and save its output
|
||||
/// var data = await ProcessRunner.
|
||||
/// GetProcessOutputAsync("dotnet", "--help");
|
||||
///
|
||||
/// // print the output
|
||||
/// data.WriteLine();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static async Task<string> GetProcessOutputAsync(
|
||||
string filename,
|
||||
string arguments,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var result = await GetProcessResultAsync(filename, arguments, ct).ConfigureAwait(false);
|
||||
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process output asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="workingDirectory">The working directory.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// The type of the result produced by this Task.
|
||||
/// </returns>
|
||||
public static async Task<string> GetProcessOutputAsync(
|
||||
string filename,
|
||||
string arguments,
|
||||
string workingDirectory,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var result = await GetProcessResultAsync(filename, arguments, workingDirectory, ct: ct).ConfigureAwait(false);
|
||||
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the process asynchronously and if the exit code is 0,
|
||||
/// returns all of the standard output text. If the exit code is something other than 0
|
||||
/// it returns the contents of standard error.
|
||||
/// This method is meant to be used for programs that output a relatively small amount
|
||||
/// of text using a different encoder.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// The type of the result produced by this Task.
|
||||
/// </returns>
|
||||
public static async Task<string> GetProcessEncodedOutputAsync(
|
||||
string filename,
|
||||
string arguments = "",
|
||||
Encoding encoding = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var result = await GetProcessResultAsync(filename, arguments, null, encoding, ct).ConfigureAwait(false);
|
||||
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a process asynchronously and returns the text of the standard output and standard error streams
|
||||
/// along with the exit code. This method is meant to be used for programs that output a relatively small
|
||||
/// amount of text.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">filename.</exception>
|
||||
public static Task<ProcessResult> GetProcessResultAsync(
|
||||
string filename,
|
||||
string arguments = "",
|
||||
CancellationToken ct = default) =>
|
||||
GetProcessResultAsync(filename, arguments, null, Definitions.CurrentAnsiEncoding, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a process asynchronously and returns the text of the standard output and standard error streams
|
||||
/// along with the exit code. This method is meant to be used for programs that output a relatively small
|
||||
/// amount of text.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="workingDirectory">The working directory.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">filename.</exception>
|
||||
/// <example>
|
||||
/// The following code describes how to run an external process using the <see cref="GetProcessResultAsync(string, string, string, Encoding, CancellationToken)" /> method.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using System.Threading.Tasks;
|
||||
/// using Unosquare.Swan.Components;
|
||||
/// static async Task Main()
|
||||
/// {
|
||||
/// // Execute a process asynchronously
|
||||
/// var data = await ProcessRunner.GetProcessResultAsync("dotnet", "--help");
|
||||
/// // print out the exit code
|
||||
/// $"{data.ExitCode}".WriteLine();
|
||||
/// // print out the output
|
||||
/// data.StandardOutput.WriteLine();
|
||||
/// // and the error if exists
|
||||
/// data.StandardError.Error();
|
||||
/// }
|
||||
/// }
|
||||
/// </code></example>
|
||||
public static async Task<ProcessResult> GetProcessResultAsync(
|
||||
string filename,
|
||||
string arguments,
|
||||
string workingDirectory,
|
||||
Encoding encoding = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (filename == null)
|
||||
throw new ArgumentNullException(nameof(filename));
|
||||
|
||||
if (encoding == null)
|
||||
encoding = Definitions.CurrentAnsiEncoding;
|
||||
|
||||
var standardOutputBuilder = new StringBuilder();
|
||||
var standardErrorBuilder = new StringBuilder();
|
||||
|
||||
var processReturn = await RunProcessAsync(
|
||||
filename,
|
||||
arguments,
|
||||
workingDirectory,
|
||||
(data, proc) => { standardOutputBuilder.Append(encoding.GetString(data)); },
|
||||
(data, proc) => { standardErrorBuilder.Append(encoding.GetString(data)); },
|
||||
encoding,
|
||||
true,
|
||||
ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new ProcessResult(processReturn, standardOutputBuilder.ToString(), standardErrorBuilder.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an external process asynchronously, providing callbacks to
|
||||
/// 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
|
||||
/// error streams by writing to the process' input stream.
|
||||
/// The exit code (return value) will be -1 for forceful termination of the process.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="workingDirectory">The working directory.</param>
|
||||
/// <param name="onOutputData">The on output data.</param>
|
||||
/// <param name="onErrorData">The on error data.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <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>
|
||||
/// <returns>
|
||||
/// Value type will be -1 for forceful termination of the process.
|
||||
/// </returns>
|
||||
public static Task<int> RunProcessAsync(
|
||||
string filename,
|
||||
string arguments,
|
||||
string workingDirectory,
|
||||
ProcessDataReceivedCallback onOutputData,
|
||||
ProcessDataReceivedCallback onErrorData,
|
||||
Encoding encoding,
|
||||
bool syncEvents = true,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (filename == null)
|
||||
throw new ArgumentNullException(nameof(filename));
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
// Setup the process and its corresponding start info
|
||||
var process = new Process
|
||||
{
|
||||
EnableRaisingEvents = false,
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = arguments,
|
||||
CreateNoWindow = true,
|
||||
FileName = filename,
|
||||
RedirectStandardError = true,
|
||||
StandardErrorEncoding = encoding,
|
||||
RedirectStandardOutput = true,
|
||||
StandardOutputEncoding = encoding,
|
||||
UseShellExecute = false,
|
||||
/// <param name="processData">The process data.</param>
|
||||
/// <param name="process">The process.</param>
|
||||
public delegate void ProcessDataReceivedCallback(Byte[] processData, Process process);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the process asynchronously and if the exit code is 0,
|
||||
/// returns all of the standard output text. If the exit code is something other than 0
|
||||
/// it returns the contents of standard error.
|
||||
/// This method is meant to be used for programs that output a relatively small amount of text.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The type of the result produced by this Task.</returns>
|
||||
public static Task<String> GetProcessOutputAsync(String filename, CancellationToken ct = default) =>
|
||||
GetProcessOutputAsync(filename, String.Empty, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the process asynchronously and if the exit code is 0,
|
||||
/// returns all of the standard output text. If the exit code is something other than 0
|
||||
/// it returns the contents of standard error.
|
||||
/// This method is meant to be used for programs that output a relatively small amount of text.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>The type of the result produced by this Task.</returns>
|
||||
/// <example>
|
||||
/// The following code explains how to run an external process using the
|
||||
/// <see cref="GetProcessOutputAsync(String, String, CancellationToken)"/> method.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using System.Threading.Tasks;
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// static async Task Main()
|
||||
/// {
|
||||
/// // execute a process and save its output
|
||||
/// var data = await ProcessRunner.
|
||||
/// GetProcessOutputAsync("dotnet", "--help");
|
||||
///
|
||||
/// // print the output
|
||||
/// data.WriteLine();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static async Task<String> GetProcessOutputAsync(
|
||||
String filename,
|
||||
String arguments,
|
||||
CancellationToken ct = default) {
|
||||
ProcessResult result = await GetProcessResultAsync(filename, arguments, ct).ConfigureAwait(false);
|
||||
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process output asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="workingDirectory">The working directory.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// The type of the result produced by this Task.
|
||||
/// </returns>
|
||||
public static async Task<String> GetProcessOutputAsync(
|
||||
String filename,
|
||||
String arguments,
|
||||
String workingDirectory,
|
||||
CancellationToken ct = default) {
|
||||
ProcessResult result = await GetProcessResultAsync(filename, arguments, workingDirectory, ct: ct).ConfigureAwait(false);
|
||||
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the process asynchronously and if the exit code is 0,
|
||||
/// returns all of the standard output text. If the exit code is something other than 0
|
||||
/// it returns the contents of standard error.
|
||||
/// This method is meant to be used for programs that output a relatively small amount
|
||||
/// of text using a different encoder.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// The type of the result produced by this Task.
|
||||
/// </returns>
|
||||
public static async Task<String> GetProcessEncodedOutputAsync(
|
||||
String filename,
|
||||
String arguments = "",
|
||||
Encoding encoding = null,
|
||||
CancellationToken ct = default) {
|
||||
ProcessResult result = await GetProcessResultAsync(filename, arguments, null, encoding, ct).ConfigureAwait(false);
|
||||
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a process asynchronously and returns the text of the standard output and standard error streams
|
||||
/// along with the exit code. This method is meant to be used for programs that output a relatively small
|
||||
/// amount of text.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">filename.</exception>
|
||||
public static Task<ProcessResult> GetProcessResultAsync(
|
||||
String filename,
|
||||
String arguments = "",
|
||||
CancellationToken ct = default) =>
|
||||
GetProcessResultAsync(filename, arguments, null, Definitions.CurrentAnsiEncoding, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a process asynchronously and returns the text of the standard output and standard error streams
|
||||
/// along with the exit code. This method is meant to be used for programs that output a relatively small
|
||||
/// amount of text.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="workingDirectory">The working directory.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// Text of the standard output and standard error streams along with the exit code as a <see cref="ProcessResult" /> instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">filename.</exception>
|
||||
/// <example>
|
||||
/// The following code describes how to run an external process using the <see cref="GetProcessResultAsync(String, String, String, Encoding, CancellationToken)" /> method.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using System.Threading.Tasks;
|
||||
/// using Unosquare.Swan.Components;
|
||||
/// static async Task Main()
|
||||
/// {
|
||||
/// // Execute a process asynchronously
|
||||
/// var data = await ProcessRunner.GetProcessResultAsync("dotnet", "--help");
|
||||
/// // print out the exit code
|
||||
/// $"{data.ExitCode}".WriteLine();
|
||||
/// // print out the output
|
||||
/// data.StandardOutput.WriteLine();
|
||||
/// // and the error if exists
|
||||
/// data.StandardError.Error();
|
||||
/// }
|
||||
/// }
|
||||
/// </code></example>
|
||||
public static async Task<ProcessResult> GetProcessResultAsync(
|
||||
String filename,
|
||||
String arguments,
|
||||
String workingDirectory,
|
||||
Encoding encoding = null,
|
||||
CancellationToken ct = default) {
|
||||
if(filename == null) {
|
||||
throw new ArgumentNullException(nameof(filename));
|
||||
}
|
||||
|
||||
if(encoding == null) {
|
||||
encoding = Definitions.CurrentAnsiEncoding;
|
||||
}
|
||||
|
||||
StringBuilder standardOutputBuilder = new StringBuilder();
|
||||
StringBuilder standardErrorBuilder = new StringBuilder();
|
||||
|
||||
Int32 processReturn = await RunProcessAsync(
|
||||
filename,
|
||||
arguments,
|
||||
workingDirectory,
|
||||
(data, proc) => standardOutputBuilder.Append(encoding.GetString(data)),
|
||||
(data, proc) => standardErrorBuilder.Append(encoding.GetString(data)),
|
||||
encoding,
|
||||
true,
|
||||
ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new ProcessResult(processReturn, standardOutputBuilder.ToString(), standardErrorBuilder.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an external process asynchronously, providing callbacks to
|
||||
/// 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
|
||||
/// error streams by writing to the process' input stream.
|
||||
/// The exit code (return value) will be -1 for forceful termination of the process.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="workingDirectory">The working directory.</param>
|
||||
/// <param name="onOutputData">The on output data.</param>
|
||||
/// <param name="onErrorData">The on error data.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <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>
|
||||
/// <returns>
|
||||
/// Value type will be -1 for forceful termination of the process.
|
||||
/// </returns>
|
||||
public static Task<Int32> RunProcessAsync(
|
||||
String filename,
|
||||
String arguments,
|
||||
String workingDirectory,
|
||||
ProcessDataReceivedCallback onOutputData,
|
||||
ProcessDataReceivedCallback onErrorData,
|
||||
Encoding encoding,
|
||||
Boolean syncEvents = true,
|
||||
CancellationToken ct = default) {
|
||||
if(filename == null) {
|
||||
throw new ArgumentNullException(nameof(filename));
|
||||
}
|
||||
|
||||
return Task.Run(() => {
|
||||
// Setup the process and its corresponding start info
|
||||
Process process = new Process {
|
||||
EnableRaisingEvents = false,
|
||||
StartInfo = new ProcessStartInfo {
|
||||
Arguments = arguments,
|
||||
CreateNoWindow = true,
|
||||
FileName = filename,
|
||||
RedirectStandardError = true,
|
||||
StandardErrorEncoding = encoding,
|
||||
RedirectStandardOutput = true,
|
||||
StandardOutputEncoding = encoding,
|
||||
UseShellExecute = false,
|
||||
#if NET452
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(workingDirectory))
|
||||
process.StartInfo.WorkingDirectory = workingDirectory;
|
||||
|
||||
// Launch the process and discard any buffered data for standard error and standard output
|
||||
process.Start();
|
||||
process.StandardError.DiscardBufferedData();
|
||||
process.StandardOutput.DiscardBufferedData();
|
||||
|
||||
// Launch the asynchronous stream reading tasks
|
||||
var readTasks = new Task[2];
|
||||
readTasks[0] = CopyStreamAsync(
|
||||
process,
|
||||
process.StandardOutput.BaseStream,
|
||||
onOutputData,
|
||||
syncEvents,
|
||||
ct);
|
||||
readTasks[1] = CopyStreamAsync(
|
||||
process,
|
||||
process.StandardError.BaseStream,
|
||||
onErrorData,
|
||||
syncEvents,
|
||||
ct);
|
||||
|
||||
try
|
||||
{
|
||||
// Wait for all tasks to complete
|
||||
Task.WaitAll(readTasks, ct);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Wait for the process to exit
|
||||
while (ct.IsCancellationRequested == false)
|
||||
{
|
||||
if (process.HasExited || process.WaitForExit(5))
|
||||
break;
|
||||
}
|
||||
|
||||
// Forcefully kill the process if it do not exit
|
||||
try
|
||||
{
|
||||
if (process.HasExited == false)
|
||||
process.Kill();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Retrieve and return the exit code.
|
||||
// -1 signals error
|
||||
return process.HasExited ? process.ExitCode : -1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an external process asynchronously, providing callbacks to
|
||||
/// 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
|
||||
/// error streams by writing to the process' input stream.
|
||||
/// The exit code (return value) will be -1 for forceful termination of the process.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="onOutputData">The on output data.</param>
|
||||
/// <param name="onErrorData">The on error data.</param>
|
||||
/// <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>
|
||||
/// <returns>Value type will be -1 for forceful termination of the process.</returns>
|
||||
/// <example>
|
||||
/// The following example illustrates how to run an external process using the
|
||||
/// <see cref="RunProcessAsync(string, string, ProcessDataReceivedCallback, ProcessDataReceivedCallback, bool, CancellationToken)"/>
|
||||
/// method.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using System.Diagnostics;
|
||||
/// using System.Text;
|
||||
/// using System.Threading.Tasks;
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// static async Task Main()
|
||||
/// {
|
||||
/// // Execute a process asynchronously
|
||||
/// var data = await ProcessRunner
|
||||
/// .RunProcessAsync("dotnet", "--help", Print, Print);
|
||||
///
|
||||
/// // flush all messages
|
||||
/// Terminal.Flush();
|
||||
/// }
|
||||
///
|
||||
/// // a callback to print both output or errors
|
||||
/// static void Print(byte[] data, Process proc) =>
|
||||
/// Encoding.GetEncoding(0).GetString(data).WriteLine();
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static Task<int> RunProcessAsync(
|
||||
string filename,
|
||||
string arguments,
|
||||
ProcessDataReceivedCallback onOutputData,
|
||||
ProcessDataReceivedCallback onErrorData,
|
||||
bool syncEvents = true,
|
||||
CancellationToken ct = default)
|
||||
=> RunProcessAsync(
|
||||
filename,
|
||||
arguments,
|
||||
null,
|
||||
onOutputData,
|
||||
onErrorData,
|
||||
Definitions.CurrentAnsiEncoding,
|
||||
syncEvents,
|
||||
ct);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the stream asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="process">The process.</param>
|
||||
/// <param name="baseStream">The source stream.</param>
|
||||
/// <param name="onDataCallback">The on data callback.</param>
|
||||
/// <param name="syncEvents">if set to <c>true</c> [synchronize events].</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>Total copies stream.</returns>
|
||||
private static Task<ulong> CopyStreamAsync(
|
||||
Process process,
|
||||
Stream baseStream,
|
||||
ProcessDataReceivedCallback onDataCallback,
|
||||
bool syncEvents,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return Task.Factory.StartNew(async () =>
|
||||
{
|
||||
// define some state variables
|
||||
var swapBuffer = new byte[2048]; // the buffer to copy data from one stream to the next
|
||||
ulong totalCount = 0; // the total amount of bytes read
|
||||
var hasExited = false;
|
||||
|
||||
while (ct.IsCancellationRequested == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if process is no longer valid
|
||||
// if this condition holds, simply read the last bits of data available.
|
||||
int readCount; // the bytes read in any given event
|
||||
if (process.HasExited || process.WaitForExit(1))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
|
||||
|
||||
if (readCount > 0)
|
||||
{
|
||||
totalCount += (ulong) readCount;
|
||||
onDataCallback?.Invoke(swapBuffer.Skip(0).Take(readCount).ToArray(), process);
|
||||
}
|
||||
else
|
||||
{
|
||||
hasExited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
hasExited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasExited) break;
|
||||
|
||||
// Try reading from the stream. < 0 means no read occurred.
|
||||
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
|
||||
|
||||
// When no read is done, we need to let is rest for a bit
|
||||
if (readCount <= 0)
|
||||
{
|
||||
await Task.Delay(1, ct); // do not hog CPU cycles doing nothing.
|
||||
continue;
|
||||
}
|
||||
|
||||
totalCount += (ulong) readCount;
|
||||
if (onDataCallback == null) continue;
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if(!String.IsNullOrWhiteSpace(workingDirectory)) {
|
||||
process.StartInfo.WorkingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
// Launch the process and discard any buffered data for standard error and standard output
|
||||
process.Start();
|
||||
process.StandardError.DiscardBufferedData();
|
||||
process.StandardOutput.DiscardBufferedData();
|
||||
|
||||
// Launch the asynchronous stream reading tasks
|
||||
Task[] readTasks = new Task[2];
|
||||
readTasks[0] = CopyStreamAsync(
|
||||
process,
|
||||
process.StandardOutput.BaseStream,
|
||||
onOutputData,
|
||||
syncEvents,
|
||||
ct);
|
||||
readTasks[1] = CopyStreamAsync(
|
||||
process,
|
||||
process.StandardError.BaseStream,
|
||||
onErrorData,
|
||||
syncEvents,
|
||||
ct);
|
||||
|
||||
try {
|
||||
// Wait for all tasks to complete
|
||||
Task.WaitAll(readTasks, ct);
|
||||
} catch(TaskCanceledException) {
|
||||
// ignore
|
||||
} finally {
|
||||
// Wait for the process to exit
|
||||
while(ct.IsCancellationRequested == false) {
|
||||
if(process.HasExited || process.WaitForExit(5)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Forcefully kill the process if it do not exit
|
||||
try {
|
||||
if(process.HasExited == false) {
|
||||
process.Kill();
|
||||
}
|
||||
} catch {
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Retrieve and return the exit code.
|
||||
// -1 signals error
|
||||
return process.HasExited ? process.ExitCode : -1;
|
||||
} catch {
|
||||
return -1;
|
||||
}
|
||||
}, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an external process asynchronously, providing callbacks to
|
||||
/// 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
|
||||
/// error streams by writing to the process' input stream.
|
||||
/// The exit code (return value) will be -1 for forceful termination of the process.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="arguments">The arguments.</param>
|
||||
/// <param name="onOutputData">The on output data.</param>
|
||||
/// <param name="onErrorData">The on error data.</param>
|
||||
/// <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>
|
||||
/// <returns>Value type will be -1 for forceful termination of the process.</returns>
|
||||
/// <example>
|
||||
/// The following example illustrates how to run an external process using the
|
||||
/// <see cref="RunProcessAsync(String, String, ProcessDataReceivedCallback, ProcessDataReceivedCallback, Boolean, CancellationToken)"/>
|
||||
/// method.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using System.Diagnostics;
|
||||
/// using System.Text;
|
||||
/// using System.Threading.Tasks;
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// static async Task Main()
|
||||
/// {
|
||||
/// // Execute a process asynchronously
|
||||
/// var data = await ProcessRunner
|
||||
/// .RunProcessAsync("dotnet", "--help", Print, Print);
|
||||
///
|
||||
/// // flush all messages
|
||||
/// Terminal.Flush();
|
||||
/// }
|
||||
///
|
||||
/// // a callback to print both output or errors
|
||||
/// static void Print(byte[] data, Process proc) =>
|
||||
/// Encoding.GetEncoding(0).GetString(data).WriteLine();
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static Task<Int32> RunProcessAsync(
|
||||
String filename,
|
||||
String arguments,
|
||||
ProcessDataReceivedCallback onOutputData,
|
||||
ProcessDataReceivedCallback onErrorData,
|
||||
Boolean syncEvents = true,
|
||||
CancellationToken ct = default)
|
||||
=> RunProcessAsync(
|
||||
filename,
|
||||
arguments,
|
||||
null,
|
||||
onOutputData,
|
||||
onErrorData,
|
||||
Definitions.CurrentAnsiEncoding,
|
||||
syncEvents,
|
||||
ct);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the stream asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="process">The process.</param>
|
||||
/// <param name="baseStream">The source stream.</param>
|
||||
/// <param name="onDataCallback">The on data callback.</param>
|
||||
/// <param name="syncEvents">if set to <c>true</c> [synchronize events].</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>Total copies stream.</returns>
|
||||
private static Task<UInt64> CopyStreamAsync(
|
||||
Process process,
|
||||
Stream baseStream,
|
||||
ProcessDataReceivedCallback onDataCallback,
|
||||
Boolean syncEvents,
|
||||
CancellationToken ct) => Task.Factory.StartNew(async () => {
|
||||
// define some state variables
|
||||
Byte[] swapBuffer = new Byte[2048]; // the buffer to copy data from one stream to the next
|
||||
UInt64 totalCount = 0; // the total amount of bytes read
|
||||
Boolean hasExited = false;
|
||||
|
||||
while(ct.IsCancellationRequested == false) {
|
||||
try {
|
||||
// Check if process is no longer valid
|
||||
// if this condition holds, simply read the last bits of data available.
|
||||
Int32 readCount; // the bytes read in any given event
|
||||
if(process.HasExited || process.WaitForExit(1)) {
|
||||
while(true) {
|
||||
try {
|
||||
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
|
||||
|
||||
if(readCount > 0) {
|
||||
totalCount += (UInt64)readCount;
|
||||
onDataCallback?.Invoke(swapBuffer.Skip(0).Take(readCount).ToArray(), process);
|
||||
} else {
|
||||
hasExited = true;
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
hasExited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(hasExited) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Try reading from the stream. < 0 means no read occurred.
|
||||
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
|
||||
|
||||
// When no read is done, we need to let is rest for a bit
|
||||
if(readCount <= 0) {
|
||||
await Task.Delay(1, ct); // do not hog CPU cycles doing nothing.
|
||||
continue;
|
||||
}
|
||||
|
||||
totalCount += (UInt64)readCount;
|
||||
if(onDataCallback == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the buffer to pass to the callback
|
||||
Byte[] eventBuffer = swapBuffer.Skip(0).Take(readCount).ToArray();
|
||||
|
||||
// Create the data processing callback invocation
|
||||
Task 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();
|
||||
}
|
||||
}
|
@ -1,143 +1,128 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Abstractions;
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Unosquare.Swan.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>
|
||||
/// A time measurement artifact.
|
||||
/// Initializes a new instance of the <see cref="RealTimeClock"/> class.
|
||||
/// The clock starts paused and at the 0 position.
|
||||
/// </summary>
|
||||
internal sealed class RealTimeClock : IDisposable
|
||||
{
|
||||
private readonly Stopwatch _chrono = new Stopwatch();
|
||||
private ISyncLocker _locker = SyncLockerFactory.Create(useSlim: true);
|
||||
private long _offsetTicks;
|
||||
private double _speedRatio = 1.0d;
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RealTimeClock"/> class.
|
||||
/// The clock starts paused and at the 0 position.
|
||||
/// </summary>
|
||||
public RealTimeClock()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the clock position.
|
||||
/// </summary>
|
||||
public TimeSpan Position
|
||||
{
|
||||
get
|
||||
{
|
||||
using (_locker.AcquireReaderLock())
|
||||
{
|
||||
return TimeSpan.FromTicks(
|
||||
_offsetTicks + Convert.ToInt64(_chrono.Elapsed.Ticks * SpeedRatio));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the clock is running.
|
||||
/// </summary>
|
||||
public bool IsRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
using (_locker.AcquireReaderLock())
|
||||
{
|
||||
return _chrono.IsRunning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the speed ratio at which the clock runs.
|
||||
/// </summary>
|
||||
public double SpeedRatio
|
||||
{
|
||||
get
|
||||
{
|
||||
using (_locker.AcquireReaderLock())
|
||||
{
|
||||
return _speedRatio;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
using (_locker.AcquireWriterLock())
|
||||
{
|
||||
if (value < 0d) value = 0d;
|
||||
|
||||
// Capture the initial position se we set it even after the speedratio has changed
|
||||
// this ensures a smooth position transition
|
||||
var initialPosition = Position;
|
||||
_speedRatio = value;
|
||||
Update(initialPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
using (_locker.AcquireWriterLock())
|
||||
{
|
||||
var resume = _chrono.IsRunning;
|
||||
_chrono.Reset();
|
||||
_offsetTicks = value.Ticks;
|
||||
if (resume) _chrono.Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts or resumes the clock.
|
||||
/// </summary>
|
||||
public void Play()
|
||||
{
|
||||
using (_locker.AcquireWriterLock())
|
||||
{
|
||||
if (_chrono.IsRunning) return;
|
||||
_chrono.Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the clock.
|
||||
/// </summary>
|
||||
public void Pause()
|
||||
{
|
||||
using (_locker.AcquireWriterLock())
|
||||
{
|
||||
_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;
|
||||
}
|
||||
}
|
||||
public RealTimeClock() => this.Reset();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the clock position.
|
||||
/// </summary>
|
||||
public TimeSpan Position {
|
||||
get {
|
||||
using(this._locker.AcquireReaderLock()) {
|
||||
return TimeSpan.FromTicks(
|
||||
this._offsetTicks + Convert.ToInt64(this._chrono.Elapsed.Ticks * this.SpeedRatio));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the clock is running.
|
||||
/// </summary>
|
||||
public Boolean IsRunning {
|
||||
get {
|
||||
using(this._locker.AcquireReaderLock()) {
|
||||
return this._chrono.IsRunning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the speed ratio at which the clock runs.
|
||||
/// </summary>
|
||||
public Double SpeedRatio {
|
||||
get {
|
||||
using(this._locker.AcquireReaderLock()) {
|
||||
return this._speedRatio;
|
||||
}
|
||||
}
|
||||
set {
|
||||
using(this._locker.AcquireWriterLock()) {
|
||||
if(value < 0d) {
|
||||
value = 0d;
|
||||
}
|
||||
|
||||
// Capture the initial position se we set it even after the speedratio has changed
|
||||
// this ensures a smooth position transition
|
||||
TimeSpan initialPosition = this.Position;
|
||||
this._speedRatio = value;
|
||||
this.Update(initialPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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) {
|
||||
using(this._locker.AcquireWriterLock()) {
|
||||
Boolean resume = this._chrono.IsRunning;
|
||||
this._chrono.Reset();
|
||||
this._offsetTicks = value.Ticks;
|
||||
if(resume) {
|
||||
this._chrono.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts or resumes the clock.
|
||||
/// </summary>
|
||||
public void Play() {
|
||||
using(this._locker.AcquireWriterLock()) {
|
||||
if(this._chrono.IsRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._chrono.Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the clock.
|
||||
/// </summary>
|
||||
public void Pause() {
|
||||
using(this._locker.AcquireWriterLock()) {
|
||||
this._chrono.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the clock position to 0 and stops it.
|
||||
/// The speed ratio is not modified.
|
||||
/// </summary>
|
||||
public void Reset() {
|
||||
using(this._locker.AcquireWriterLock()) {
|
||||
this._offsetTicks = 0;
|
||||
this._chrono.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
this._locker?.Dispose();
|
||||
this._locker = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,132 +1,120 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Exceptions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unosquare.Swan.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>
|
||||
/// Registration options for "fluent" API.
|
||||
/// Initializes a new instance of the <see cref="RegisterOptions" /> class.
|
||||
/// </summary>
|
||||
public sealed class RegisterOptions
|
||||
{
|
||||
private readonly TypesConcurrentDictionary _registeredTypes;
|
||||
private readonly DependencyContainer.TypeRegistration _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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="registeredTypes">The registered types.</param>
|
||||
/// <param name="registration">The registration.</param>
|
||||
public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration) {
|
||||
this._registeredTypes = registeredTypes;
|
||||
this._registration = registration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registration options for "fluent" API when registering multiple implementations.
|
||||
/// Make registration a singleton (single instance) if possible.
|
||||
/// </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)
|
||||
{
|
||||
_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()
|
||||
{
|
||||
_registerOptions = 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()
|
||||
{
|
||||
_registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance());
|
||||
return this;
|
||||
}
|
||||
|
||||
private IEnumerable<RegisterOptions> ExecuteOnAllRegisterOptions(
|
||||
Func<RegisterOptions, RegisterOptions> action)
|
||||
{
|
||||
return _registerOptions.Select(action).ToList();
|
||||
}
|
||||
}
|
||||
/// <returns>A registration options for fluent API.</returns>
|
||||
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
|
||||
public RegisterOptions AsSingleton() {
|
||||
ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
|
||||
|
||||
if(currentFactory == null) {
|
||||
throw new DependencyContainerRegistrationException(this._registration.Type, "singleton");
|
||||
}
|
||||
|
||||
return this._registeredTypes.AddUpdateRegistration(this._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() {
|
||||
ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
|
||||
|
||||
if(currentFactory == null) {
|
||||
throw new DependencyContainerRegistrationException(this._registration.Type, "multi-instance");
|
||||
}
|
||||
|
||||
return this._registeredTypes.AddUpdateRegistration(this._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() {
|
||||
ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
|
||||
|
||||
if(currentFactory == null) {
|
||||
throw new DependencyContainerRegistrationException(this._registration.Type, "weak reference");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,67 +1,63 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
|
||||
public partial class DependencyContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Type Registration within the IoC Container.
|
||||
/// </summary>
|
||||
public sealed class TypeRegistration
|
||||
{
|
||||
private readonly int _hashCode;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeRegistration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
public TypeRegistration(Type type, string name = null)
|
||||
{
|
||||
Type = type;
|
||||
Name = name ?? string.Empty;
|
||||
|
||||
_hashCode = string.Concat(Type.FullName, "|", Name).GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is TypeRegistration typeRegistration) || typeRegistration.Type != Type)
|
||||
return false;
|
||||
|
||||
return string.Compare(Name, typeRegistration.Name, StringComparison.Ordinal) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Components {
|
||||
public partial class DependencyContainer {
|
||||
/// <summary>
|
||||
/// Represents a Type Registration within the IoC Container.
|
||||
/// </summary>
|
||||
public sealed class TypeRegistration {
|
||||
private readonly Int32 _hashCode;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeRegistration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
public TypeRegistration(Type type, String name = null) {
|
||||
this.Type = type;
|
||||
this.Name = name ?? String.Empty;
|
||||
|
||||
this._hashCode = String.Concat(this.Type.FullName, "|", this.Name).GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public Type Type {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public String Name {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override Boolean Equals(Object obj) => !(obj is TypeRegistration typeRegistration) || typeRegistration.Type != this.Type
|
||||
? false
|
||||
: String.Compare(this.Name, typeRegistration.Name, StringComparison.Ordinal) == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override Int32 GetHashCode() => this._hashCode;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,352 +1,308 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Exceptions;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unosquare.Swan.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>
|
||||
/// Represents a Concurrent Dictionary for TypeRegistration.
|
||||
/// Represents a delegate to build an object with the parameters.
|
||||
/// </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)
|
||||
{
|
||||
_dependencyContainer = dependencyContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a delegate to build an object with the parameters.
|
||||
/// </summary>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns>The built object.</returns>
|
||||
public delegate object ObjectConstructor(params object[] parameters);
|
||||
|
||||
internal IEnumerable<object> Resolve(Type resolveType, bool includeUnnamed)
|
||||
{
|
||||
var registrations = Keys.Where(tr => tr.Type == resolveType)
|
||||
.Concat(GetParentRegistrationsForType(resolveType)).Distinct();
|
||||
|
||||
if (!includeUnnamed)
|
||||
registrations = registrations.Where(tr => tr.Name != string.Empty);
|
||||
|
||||
return registrations.Select(registration =>
|
||||
ResolveInternal(registration, DependencyContainerResolveOptions.Default));
|
||||
}
|
||||
|
||||
internal ObjectFactoryBase GetCurrentFactory(DependencyContainer.TypeRegistration registration)
|
||||
{
|
||||
TryGetValue(registration, out var current);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
internal RegisterOptions Register(Type registerType, string name, ObjectFactoryBase factory)
|
||||
=> AddUpdateRegistration(new DependencyContainer.TypeRegistration(registerType, name), factory);
|
||||
|
||||
internal RegisterOptions AddUpdateRegistration(DependencyContainer.TypeRegistration typeRegistration, ObjectFactoryBase factory)
|
||||
{
|
||||
this[typeRegistration] = factory;
|
||||
|
||||
return new RegisterOptions(this, typeRegistration);
|
||||
}
|
||||
|
||||
internal bool RemoveRegistration(DependencyContainer.TypeRegistration typeRegistration)
|
||||
=> TryRemove(typeRegistration, out _);
|
||||
|
||||
internal object ResolveInternal(
|
||||
DependencyContainer.TypeRegistration registration,
|
||||
DependencyContainerResolveOptions options = null)
|
||||
{
|
||||
if (options == null)
|
||||
options = DependencyContainerResolveOptions.Default;
|
||||
|
||||
// Attempt container resolution
|
||||
if (TryGetValue(registration, out var factory))
|
||||
{
|
||||
try
|
||||
{
|
||||
return factory.GetObject(registration.Type, _dependencyContainer, options);
|
||||
}
|
||||
catch (DependencyContainerResolutionException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DependencyContainerResolutionException(registration.Type, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to get a factory from parent if we can
|
||||
var bubbledObjectFactory = GetParentObjectFactory(registration);
|
||||
if (bubbledObjectFactory != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return bubbledObjectFactory.GetObject(registration.Type, _dependencyContainer, options);
|
||||
}
|
||||
catch (DependencyContainerResolutionException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DependencyContainerResolutionException(registration.Type, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Fail if requesting named resolution and settings set to fail if unresolved
|
||||
if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
|
||||
DependencyContainerNamedResolutionFailureActions.Fail)
|
||||
throw new DependencyContainerResolutionException(registration.Type);
|
||||
|
||||
// Attempted unnamed fallback container resolution if relevant and requested
|
||||
if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
|
||||
DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution)
|
||||
{
|
||||
if (TryGetValue(new DependencyContainer.TypeRegistration(registration.Type, string.Empty), out factory))
|
||||
{
|
||||
try
|
||||
{
|
||||
return factory.GetObject(registration.Type, _dependencyContainer, options);
|
||||
}
|
||||
catch (DependencyContainerResolutionException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DependencyContainerResolutionException(registration.Type, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt unregistered construction if possible and requested
|
||||
var isValid = (options.UnregisteredResolutionAction ==
|
||||
DependencyContainerUnregisteredResolutionActions.AttemptResolve) ||
|
||||
(registration.Type.IsGenericType() && options.UnregisteredResolutionAction ==
|
||||
DependencyContainerUnregisteredResolutionActions.GenericsOnly);
|
||||
|
||||
return isValid && !registration.Type.IsAbstract() && !registration.Type.IsInterface()
|
||||
? ConstructType(registration.Type, null, options)
|
||||
: throw new DependencyContainerResolutionException(registration.Type);
|
||||
}
|
||||
|
||||
internal bool CanResolve(
|
||||
DependencyContainer.TypeRegistration registration,
|
||||
DependencyContainerResolveOptions options = null)
|
||||
{
|
||||
if (options == null)
|
||||
options = DependencyContainerResolveOptions.Default;
|
||||
|
||||
var checkType = registration.Type;
|
||||
var name = registration.Name;
|
||||
|
||||
if (TryGetValue(new DependencyContainer.TypeRegistration(checkType, name), out var factory))
|
||||
{
|
||||
if (factory.AssumeConstruction)
|
||||
return true;
|
||||
|
||||
if (factory.Constructor == null)
|
||||
return GetBestConstructor(factory.CreatesType, options) != null;
|
||||
|
||||
return CanConstruct(factory.Constructor, options);
|
||||
}
|
||||
|
||||
// Fail if requesting named resolution and settings set to fail if unresolved
|
||||
// Or bubble up if we have a parent
|
||||
if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
|
||||
DependencyContainerNamedResolutionFailureActions.Fail)
|
||||
return _dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false;
|
||||
|
||||
// Attempted unnamed fallback container resolution if relevant and requested
|
||||
if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
|
||||
DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution)
|
||||
{
|
||||
if (TryGetValue(new DependencyContainer.TypeRegistration(checkType), out factory))
|
||||
{
|
||||
if (factory.AssumeConstruction)
|
||||
return true;
|
||||
|
||||
return GetBestConstructor(factory.CreatesType, options) != null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if type is an automatic lazy factory request or an IEnumerable<ResolveType>
|
||||
if (IsAutomaticLazyFactoryRequest(checkType) || registration.Type.IsIEnumerable())
|
||||
return true;
|
||||
|
||||
// Attempt unregistered construction if possible and requested
|
||||
// If we cant', bubble if we have a parent
|
||||
if ((options.UnregisteredResolutionAction ==
|
||||
DependencyContainerUnregisteredResolutionActions.AttemptResolve) ||
|
||||
(checkType.IsGenericType() && options.UnregisteredResolutionAction ==
|
||||
DependencyContainerUnregisteredResolutionActions.GenericsOnly))
|
||||
{
|
||||
return (GetBestConstructor(checkType, options) != null) ||
|
||||
(_dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false);
|
||||
}
|
||||
|
||||
// Bubble resolution up the container tree if we have a parent
|
||||
return _dependencyContainer.Parent != null && _dependencyContainer.Parent.RegisteredTypes.CanResolve(registration, options.Clone());
|
||||
}
|
||||
|
||||
internal object ConstructType(
|
||||
Type implementationType,
|
||||
ConstructorInfo constructor,
|
||||
DependencyContainerResolveOptions options = null)
|
||||
{
|
||||
var typeToConstruct = implementationType;
|
||||
|
||||
if (constructor == null)
|
||||
{
|
||||
// Try and get the best constructor that we can construct
|
||||
// if we can't construct any then get the constructor
|
||||
// with the least number of parameters so we can throw a meaningful
|
||||
// resolve exception
|
||||
constructor = GetBestConstructor(typeToConstruct, options) ??
|
||||
GetTypeConstructors(typeToConstruct).LastOrDefault();
|
||||
}
|
||||
|
||||
if (constructor == null)
|
||||
throw new DependencyContainerResolutionException(typeToConstruct);
|
||||
|
||||
var ctorParams = constructor.GetParameters();
|
||||
var args = new object[ctorParams.Length];
|
||||
|
||||
for (var parameterIndex = 0; parameterIndex < ctorParams.Length; parameterIndex++)
|
||||
{
|
||||
var currentParam = ctorParams[parameterIndex];
|
||||
|
||||
try
|
||||
{
|
||||
args[parameterIndex] = options?.ConstructorParameters.GetValueOrDefault(currentParam.Name, ResolveInternal(new DependencyContainer.TypeRegistration(currentParam.ParameterType), options.Clone()));
|
||||
}
|
||||
catch (DependencyContainerResolutionException ex)
|
||||
{
|
||||
// If a constructor parameter can't be resolved
|
||||
// it will throw, so wrap it and throw that this can't
|
||||
// be resolved.
|
||||
throw new DependencyContainerResolutionException(typeToConstruct, ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DependencyContainerResolutionException(typeToConstruct, ex);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return CreateObjectConstructionDelegateWithCache(constructor).Invoke(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DependencyContainerResolutionException(typeToConstruct, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor)
|
||||
{
|
||||
if (ObjectConstructorCache.TryGetValue(constructor, out var objectConstructor))
|
||||
return objectConstructor;
|
||||
|
||||
// We could lock the cache here, but there's no real side
|
||||
// effect to two threads creating the same ObjectConstructor
|
||||
// at the same time, compared to the cost of a lock for
|
||||
// every creation.
|
||||
var constructorParams = constructor.GetParameters();
|
||||
var lambdaParams = Expression.Parameter(typeof(object[]), "parameters");
|
||||
var newParams = new Expression[constructorParams.Length];
|
||||
|
||||
for (var i = 0; i < constructorParams.Length; i++)
|
||||
{
|
||||
var paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i));
|
||||
|
||||
newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType);
|
||||
}
|
||||
|
||||
var newExpression = Expression.New(constructor, newParams);
|
||||
|
||||
var constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams);
|
||||
|
||||
objectConstructor = (ObjectConstructor)constructionLambda.Compile();
|
||||
|
||||
ObjectConstructorCache[constructor] = objectConstructor;
|
||||
return objectConstructor;
|
||||
}
|
||||
|
||||
private static IEnumerable<ConstructorInfo> GetTypeConstructors(Type type)
|
||||
=> type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length);
|
||||
|
||||
private static bool IsAutomaticLazyFactoryRequest(Type type)
|
||||
{
|
||||
if (!type.IsGenericType())
|
||||
return false;
|
||||
|
||||
var genericType = type.GetGenericTypeDefinition();
|
||||
|
||||
// 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));
|
||||
}
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns>The built object.</returns>
|
||||
public delegate Object ObjectConstructor(params Object[] parameters);
|
||||
|
||||
internal IEnumerable<Object> Resolve(Type resolveType, Boolean includeUnnamed) {
|
||||
IEnumerable<DependencyContainer.TypeRegistration> registrations = this.Keys.Where(tr => tr.Type == resolveType)
|
||||
.Concat(this.GetParentRegistrationsForType(resolveType)).Distinct();
|
||||
|
||||
if(!includeUnnamed) {
|
||||
registrations = registrations.Where(tr => tr.Name != String.Empty);
|
||||
}
|
||||
|
||||
return registrations.Select(registration =>
|
||||
this.ResolveInternal(registration, DependencyContainerResolveOptions.Default));
|
||||
}
|
||||
|
||||
internal ObjectFactoryBase GetCurrentFactory(DependencyContainer.TypeRegistration registration) {
|
||||
_ = this.TryGetValue(registration, out ObjectFactoryBase current);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
internal RegisterOptions Register(Type registerType, String name, ObjectFactoryBase factory)
|
||||
=> this.AddUpdateRegistration(new DependencyContainer.TypeRegistration(registerType, name), factory);
|
||||
|
||||
internal RegisterOptions AddUpdateRegistration(DependencyContainer.TypeRegistration typeRegistration, ObjectFactoryBase factory) {
|
||||
this[typeRegistration] = factory;
|
||||
|
||||
return new RegisterOptions(this, typeRegistration);
|
||||
}
|
||||
|
||||
internal Boolean RemoveRegistration(DependencyContainer.TypeRegistration typeRegistration)
|
||||
=> this.TryRemove(typeRegistration, out _);
|
||||
|
||||
internal Object ResolveInternal(
|
||||
DependencyContainer.TypeRegistration registration,
|
||||
DependencyContainerResolveOptions options = null) {
|
||||
if(options == null) {
|
||||
options = DependencyContainerResolveOptions.Default;
|
||||
}
|
||||
|
||||
// Attempt container resolution
|
||||
if(this.TryGetValue(registration, out ObjectFactoryBase factory)) {
|
||||
try {
|
||||
return factory.GetObject(registration.Type, this._dependencyContainer, options);
|
||||
} catch(DependencyContainerResolutionException) {
|
||||
throw;
|
||||
} catch(Exception ex) {
|
||||
throw new DependencyContainerResolutionException(registration.Type, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to get a factory from parent if we can
|
||||
ObjectFactoryBase bubbledObjectFactory = this.GetParentObjectFactory(registration);
|
||||
if(bubbledObjectFactory != null) {
|
||||
try {
|
||||
return bubbledObjectFactory.GetObject(registration.Type, this._dependencyContainer, options);
|
||||
} catch(DependencyContainerResolutionException) {
|
||||
throw;
|
||||
} catch(Exception ex) {
|
||||
throw new DependencyContainerResolutionException(registration.Type, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Fail if requesting named resolution and settings set to fail if unresolved
|
||||
if(!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
|
||||
DependencyContainerNamedResolutionFailureActions.Fail) {
|
||||
throw new DependencyContainerResolutionException(registration.Type);
|
||||
}
|
||||
|
||||
// Attempted unnamed fallback container resolution if relevant and requested
|
||||
if(!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
|
||||
DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution) {
|
||||
if(this.TryGetValue(new DependencyContainer.TypeRegistration(registration.Type, String.Empty), out factory)) {
|
||||
try {
|
||||
return factory.GetObject(registration.Type, this._dependencyContainer, options);
|
||||
} catch(DependencyContainerResolutionException) {
|
||||
throw;
|
||||
} catch(Exception ex) {
|
||||
throw new DependencyContainerResolutionException(registration.Type, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt unregistered construction if possible and requested
|
||||
Boolean isValid = options.UnregisteredResolutionAction ==
|
||||
DependencyContainerUnregisteredResolutionActions.AttemptResolve ||
|
||||
registration.Type.IsGenericType() && options.UnregisteredResolutionAction ==
|
||||
DependencyContainerUnregisteredResolutionActions.GenericsOnly;
|
||||
|
||||
return isValid && !registration.Type.IsAbstract() && !registration.Type.IsInterface()
|
||||
? this.ConstructType(registration.Type, null, options)
|
||||
: throw new DependencyContainerResolutionException(registration.Type);
|
||||
}
|
||||
|
||||
internal Boolean CanResolve(
|
||||
DependencyContainer.TypeRegistration registration,
|
||||
DependencyContainerResolveOptions options = null) {
|
||||
if(options == null) {
|
||||
options = DependencyContainerResolveOptions.Default;
|
||||
}
|
||||
|
||||
Type checkType = registration.Type;
|
||||
String name = registration.Name;
|
||||
|
||||
if(this.TryGetValue(new DependencyContainer.TypeRegistration(checkType, name), out ObjectFactoryBase factory)) {
|
||||
return factory.AssumeConstruction
|
||||
? true
|
||||
: factory.Constructor == null
|
||||
? this.GetBestConstructor(factory.CreatesType, options) != null
|
||||
: this.CanConstruct(factory.Constructor, options);
|
||||
}
|
||||
|
||||
// Fail if requesting named resolution and settings set to fail if unresolved
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Attempted unnamed fallback container resolution if relevant and requested
|
||||
if(!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
|
||||
DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution) {
|
||||
if(this.TryGetValue(new DependencyContainer.TypeRegistration(checkType), out factory)) {
|
||||
return factory.AssumeConstruction ? true : this.GetBestConstructor(factory.CreatesType, options) != null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if type is an automatic lazy factory request or an IEnumerable<ResolveType>
|
||||
if(IsAutomaticLazyFactoryRequest(checkType) || registration.Type.IsIEnumerable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt unregistered construction if possible and requested
|
||||
// If we cant', bubble if we have a parent
|
||||
if(options.UnregisteredResolutionAction ==
|
||||
DependencyContainerUnregisteredResolutionActions.AttemptResolve ||
|
||||
checkType.IsGenericType() && options.UnregisteredResolutionAction ==
|
||||
DependencyContainerUnregisteredResolutionActions.GenericsOnly) {
|
||||
return this.GetBestConstructor(checkType, options) != null ||
|
||||
(this._dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false);
|
||||
}
|
||||
|
||||
// Bubble resolution up the container tree if we have a parent
|
||||
return this._dependencyContainer.Parent != null && this._dependencyContainer.Parent.RegisteredTypes.CanResolve(registration, options.Clone());
|
||||
}
|
||||
|
||||
internal Object ConstructType(
|
||||
Type implementationType,
|
||||
ConstructorInfo constructor,
|
||||
DependencyContainerResolveOptions options = null) {
|
||||
Type typeToConstruct = implementationType;
|
||||
|
||||
if(constructor == null) {
|
||||
// Try and get the best constructor that we can construct
|
||||
// if we can't construct any then get the constructor
|
||||
// with the least number of parameters so we can throw a meaningful
|
||||
// resolve exception
|
||||
constructor = this.GetBestConstructor(typeToConstruct, options) ??
|
||||
GetTypeConstructors(typeToConstruct).LastOrDefault();
|
||||
}
|
||||
|
||||
if(constructor == null) {
|
||||
throw new DependencyContainerResolutionException(typeToConstruct);
|
||||
}
|
||||
|
||||
ParameterInfo[] ctorParams = constructor.GetParameters();
|
||||
Object[] args = new Object[ctorParams.Length];
|
||||
|
||||
for(Int32 parameterIndex = 0; parameterIndex < ctorParams.Length; parameterIndex++) {
|
||||
ParameterInfo currentParam = ctorParams[parameterIndex];
|
||||
|
||||
try {
|
||||
args[parameterIndex] = options?.ConstructorParameters.GetValueOrDefault(currentParam.Name, this.ResolveInternal(new DependencyContainer.TypeRegistration(currentParam.ParameterType), options.Clone()));
|
||||
} catch(DependencyContainerResolutionException ex) {
|
||||
// If a constructor parameter can't be resolved
|
||||
// it will throw, so wrap it and throw that this can't
|
||||
// be resolved.
|
||||
throw new DependencyContainerResolutionException(typeToConstruct, ex);
|
||||
} catch(Exception ex) {
|
||||
throw new DependencyContainerResolutionException(typeToConstruct, ex);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return CreateObjectConstructionDelegateWithCache(constructor).Invoke(args);
|
||||
} catch(Exception ex) {
|
||||
throw new DependencyContainerResolutionException(typeToConstruct, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor) {
|
||||
if(ObjectConstructorCache.TryGetValue(constructor, out ObjectConstructor objectConstructor)) {
|
||||
return objectConstructor;
|
||||
}
|
||||
|
||||
// We could lock the cache here, but there's no real side
|
||||
// effect to two threads creating the same ObjectConstructor
|
||||
// at the same time, compared to the cost of a lock for
|
||||
// every creation.
|
||||
ParameterInfo[] constructorParams = constructor.GetParameters();
|
||||
ParameterExpression lambdaParams = Expression.Parameter(typeof(Object[]), "parameters");
|
||||
Expression[] newParams = new Expression[constructorParams.Length];
|
||||
|
||||
for(Int32 i = 0; i < constructorParams.Length; i++) {
|
||||
BinaryExpression paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i));
|
||||
|
||||
newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType);
|
||||
}
|
||||
|
||||
NewExpression newExpression = Expression.New(constructor, newParams);
|
||||
|
||||
LambdaExpression constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams);
|
||||
|
||||
objectConstructor = (ObjectConstructor)constructionLambda.Compile();
|
||||
|
||||
ObjectConstructorCache[constructor] = objectConstructor;
|
||||
return objectConstructor;
|
||||
}
|
||||
|
||||
private static IEnumerable<ConstructorInfo> GetTypeConstructors(Type type)
|
||||
=> type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length);
|
||||
|
||||
private static Boolean IsAutomaticLazyFactoryRequest(Type type) {
|
||||
if(!type.IsGenericType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Type genericType = type.GetGenericTypeDefinition();
|
||||
|
||||
// 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) => this._dependencyContainer.Parent == null
|
||||
? null
|
||||
: this._dependencyContainer.Parent.RegisteredTypes.TryGetValue(registration, out ObjectFactoryBase factory)
|
||||
? factory.GetFactoryForChildContainer(registration.Type, this._dependencyContainer.Parent, this._dependencyContainer)
|
||||
: this._dependencyContainer.Parent.RegisteredTypes.GetParentObjectFactory(registration);
|
||||
|
||||
private ConstructorInfo GetBestConstructor(
|
||||
Type type,
|
||||
DependencyContainerResolveOptions options)
|
||||
=> type.IsValueType() ? null : GetTypeConstructors(type).FirstOrDefault(ctor => this.CanConstruct(ctor, options));
|
||||
|
||||
private Boolean CanConstruct(
|
||||
ConstructorInfo ctor,
|
||||
DependencyContainerResolveOptions options) {
|
||||
foreach(ParameterInfo parameter in ctor.GetParameters()) {
|
||||
if(String.IsNullOrEmpty(parameter.Name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Boolean isParameterOverload = options.ConstructorParameters.ContainsKey(parameter.Name);
|
||||
|
||||
if(parameter.ParameterType.IsPrimitive() && !isParameterOverload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isParameterOverload &&
|
||||
!this.CanResolve(new DependencyContainer.TypeRegistration(parameter.ParameterType), options.Clone())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<DependencyContainer.TypeRegistration> GetParentRegistrationsForType(Type resolveType)
|
||||
=> this._dependencyContainer.Parent == null
|
||||
? new DependencyContainer.TypeRegistration[] { }
|
||||
: this._dependencyContainer.Parent.RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(this._dependencyContainer.Parent.RegisteredTypes.GetParentRegistrationsForType(resolveType));
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
/// Enumerates the possible causes of the DataReceived event occurring.
|
||||
/// The trigger was a forceful flush of the buffer
|
||||
/// </summary>
|
||||
public enum ConnectionDataReceivedTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// The trigger was a forceful flush of the buffer
|
||||
/// </summary>
|
||||
Flush,
|
||||
|
||||
/// <summary>
|
||||
/// The new line sequence bytes were received
|
||||
/// </summary>
|
||||
NewLineSequenceEncountered,
|
||||
|
||||
/// <summary>
|
||||
/// The buffer was full
|
||||
/// </summary>
|
||||
BufferFull,
|
||||
|
||||
/// <summary>
|
||||
/// The block size reached
|
||||
/// </summary>
|
||||
BlockSizeReached,
|
||||
}
|
||||
Flush,
|
||||
|
||||
/// <summary>
|
||||
/// The new line sequence bytes were received
|
||||
/// </summary>
|
||||
NewLineSequenceEncountered,
|
||||
|
||||
/// <summary>
|
||||
/// The buffer was full
|
||||
/// </summary>
|
||||
BufferFull,
|
||||
|
||||
/// <summary>
|
||||
/// The block size reached
|
||||
/// </summary>
|
||||
BlockSizeReached,
|
||||
}
|
||||
}
|
||||
|
@ -1,158 +1,157 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
using System;
|
||||
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>
|
||||
/// The event arguments for when connections are accepted.
|
||||
/// Initializes a new instance of the <see cref="ConnectionAcceptedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class ConnectionAcceptedEventArgs : EventArgs
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
|
||||
/// <param name="client">The client.</param>
|
||||
/// <exception cref="ArgumentNullException">client.</exception>
|
||||
public ConnectionAcceptedEventArgs(TcpClient client) => this.Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before a connection is accepted. Set the Cancel property to true to prevent the connection from being accepted.
|
||||
/// Gets the client.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.ConnectionAcceptedEventArgs" />
|
||||
public class ConnectionAcceptingEventArgs : ConnectionAcceptedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionAcceptingEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="client">The client.</param>
|
||||
public ConnectionAcceptingEventArgs(TcpClient client)
|
||||
: base(client)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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; }
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The client.
|
||||
/// </value>
|
||||
public TcpClient Client {
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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" />
|
||||
public class ConnectionAcceptingEventArgs : ConnectionAcceptedEventArgs {
|
||||
/// <summary>
|
||||
/// Event arguments for when a server listener is started.
|
||||
/// Initializes a new instance of the <see cref="ConnectionAcceptingEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class ConnectionListenerStartedEventArgs : EventArgs
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
|
||||
/// <param name="client">The client.</param>
|
||||
public ConnectionAcceptingEventArgs(TcpClient client)
|
||||
: base(client) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for when a server listener fails to start.
|
||||
/// Setting Cancel to true rejects the new TcpClient.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class ConnectionListenerFailedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionListenerFailedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="listenerEndPoint">The listener end point.</param>
|
||||
/// <param name="ex">The ex.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// listenerEndPoint
|
||||
/// or
|
||||
/// ex.
|
||||
/// </exception>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// <c>true</c> if cancel; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean Cancel {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for when a server listener is started.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class ConnectionListenerStartedEventArgs : EventArgs {
|
||||
/// <summary>
|
||||
/// Event arguments for when a server listener stopped.
|
||||
/// Initializes a new instance of the <see cref="ConnectionListenerStartedEventArgs" /> class.
|
||||
/// </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)
|
||||
{
|
||||
EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
|
||||
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; }
|
||||
}
|
||||
/// <param name="listenerEndPoint">The listener end point.</param>
|
||||
/// <exception cref="ArgumentNullException">listenerEndPoint.</exception>
|
||||
public ConnectionListenerStartedEventArgs(IPEndPoint listenerEndPoint) => this.EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end point.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The end point.
|
||||
/// </value>
|
||||
public IPEndPoint EndPoint {
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for when a server listener fails to start.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class ConnectionListenerFailedEventArgs : EventArgs {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionListenerFailedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="listenerEndPoint">The listener end point.</param>
|
||||
/// <param name="ex">The ex.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// listenerEndPoint
|
||||
/// or
|
||||
/// ex.
|
||||
/// </exception>
|
||||
public ConnectionListenerFailedEventArgs(IPEndPoint listenerEndPoint, Exception ex) {
|
||||
this.EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
|
||||
this.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>
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// The event arguments for connection failure events.
|
||||
/// Initializes a new instance of the <see cref="ConnectionFailureEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class ConnectionFailureEventArgs : EventArgs
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
|
||||
/// <param name="ex">The ex.</param>
|
||||
public ConnectionFailureEventArgs(Exception ex) => this.Error = ex;
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for when data is received.
|
||||
/// Gets the error.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class ConnectionDataReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionDataReceivedEventArgs"/> class.
|
||||
/// </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>
|
||||
public ConnectionDataReceivedEventArgs(byte[] buffer, ConnectionDataReceivedTrigger trigger, bool moreAvailable)
|
||||
{
|
||||
Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
|
||||
Trigger = trigger;
|
||||
HasMoreAvailable = moreAvailable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the buffer.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The buffer.
|
||||
/// </value>
|
||||
public byte[] Buffer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cause as to why this event was thrown.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The trigger.
|
||||
/// </value>
|
||||
public ConnectionDataReceivedTrigger Trigger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the receive buffer has more bytes available.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance has more available; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool HasMoreAvailable { get; }
|
||||
|
||||
/// <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(Buffer, encoding);
|
||||
}
|
||||
/// <value>
|
||||
/// The error.
|
||||
/// </value>
|
||||
public Exception Error {
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for when data is received.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class ConnectionDataReceivedEventArgs : EventArgs {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionDataReceivedEventArgs"/> class.
|
||||
/// </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>
|
||||
public ConnectionDataReceivedEventArgs(Byte[] buffer, ConnectionDataReceivedTrigger trigger, Boolean moreAvailable) {
|
||||
this.Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
|
||||
this.Trigger = trigger;
|
||||
this.HasMoreAvailable = moreAvailable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the buffer.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The buffer.
|
||||
/// </value>
|
||||
public Byte[] Buffer {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cause as to why this event was thrown.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The trigger.
|
||||
/// </value>
|
||||
public ConnectionDataReceivedTrigger Trigger {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the receive buffer has more bytes available.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance has more available; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean HasMoreAvailable {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,39 @@
|
||||
namespace Unosquare.Swan.Exceptions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using System;
|
||||
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>
|
||||
/// Generic Constraint Registration Exception.
|
||||
/// Initializes a new instance of the <see cref="DependencyContainerRegistrationException"/> class.
|
||||
/// </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>
|
||||
/// Initializes a new instance of the <see cref="DependencyContainerRegistrationException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="registerType">Type of the register.</param>
|
||||
/// <param name="types">The types.</param>
|
||||
public DependencyContainerRegistrationException(Type registerType, IEnumerable<Type> types)
|
||||
: base(string.Format(ErrorText, registerType, GetTypesString(types)))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
/// <param name="registerType">Type of the register.</param>
|
||||
/// <param name="types">The types.</param>
|
||||
public DependencyContainerRegistrationException(Type registerType, IEnumerable<Type> types)
|
||||
: base(String.Format(ErrorText, registerType, GetTypesString(types))) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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, Boolean isTypeFactory = false)
|
||||
: base(isTypeFactory
|
||||
? String.Format(RegisterErrorText, type.FullName, method)
|
||||
: String.Format(ConvertErrorText, type.FullName, method)) {
|
||||
}
|
||||
|
||||
private static String GetTypesString(IEnumerable<Type> types) => String.Join(",", types.Select(type => type.FullName));
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// An exception for dependency resolutions.
|
||||
/// Initializes a new instance of the <see cref="DependencyContainerResolutionException"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Exception" />
|
||||
public class DependencyContainerResolutionException : Exception
|
||||
{
|
||||
private const string ErrorText = "Unable to resolve type: {0}";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DependencyContainerResolutionException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
public DependencyContainerResolutionException(Type type)
|
||||
: base(string.Format(ErrorText, type.FullName))
|
||||
{
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
}
|
||||
}
|
||||
/// <param name="type">The type.</param>
|
||||
public DependencyContainerResolutionException(Type type)
|
||||
: base(String.Format(ErrorText, type.FullName)) {
|
||||
}
|
||||
|
||||
/// <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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
/// Weak Reference Exception.
|
||||
/// Initializes a new instance of the <see cref="DependencyContainerWeakReferenceException"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Exception" />
|
||||
public class DependencyContainerWeakReferenceException : Exception
|
||||
{
|
||||
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))
|
||||
{
|
||||
}
|
||||
}
|
||||
/// <param name="type">The type.</param>
|
||||
public DependencyContainerWeakReferenceException(Type type)
|
||||
: base(String.Format(ErrorText, type.FullName)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,31 @@
|
||||
namespace Unosquare.Swan.Exceptions
|
||||
{
|
||||
using System;
|
||||
using Networking;
|
||||
|
||||
/// <summary>
|
||||
/// An exception thrown when the DNS query fails.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Exception" />
|
||||
public class DnsQueryException : Exception
|
||||
{
|
||||
internal DnsQueryException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
internal DnsQueryException(string message, Exception e)
|
||||
: base(message, e)
|
||||
{
|
||||
}
|
||||
|
||||
internal DnsQueryException(DnsClient.IDnsResponse response)
|
||||
: this(response, Format(response))
|
||||
{
|
||||
}
|
||||
|
||||
internal DnsQueryException(DnsClient.IDnsResponse response, string message)
|
||||
: base(message)
|
||||
{
|
||||
Response = response;
|
||||
}
|
||||
|
||||
internal DnsClient.IDnsResponse Response { get; }
|
||||
|
||||
private static string Format(DnsClient.IDnsResponse response)
|
||||
{
|
||||
return $"Invalid response received with code {response.ResponseCode}";
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using Unosquare.Swan.Networking;
|
||||
|
||||
namespace Unosquare.Swan.Exceptions {
|
||||
/// <summary>
|
||||
/// An exception thrown when the DNS query fails.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Exception" />
|
||||
public class DnsQueryException : Exception {
|
||||
internal DnsQueryException(String message)
|
||||
: base(message) {
|
||||
}
|
||||
|
||||
internal DnsQueryException(String message, Exception e)
|
||||
: base(message, e) {
|
||||
}
|
||||
|
||||
internal DnsQueryException(DnsClient.IDnsResponse response)
|
||||
: this(response, Format(response)) {
|
||||
}
|
||||
|
||||
internal DnsQueryException(DnsClient.IDnsResponse response, String message)
|
||||
: base(message) => this.Response = response;
|
||||
|
||||
internal DnsClient.IDnsResponse Response {
|
||||
get;
|
||||
}
|
||||
|
||||
private static String Format(DnsClient.IDnsResponse response) => $"Invalid response received with code {response.ResponseCode}";
|
||||
}
|
||||
}
|
@ -1,48 +1,49 @@
|
||||
namespace Unosquare.Swan.Exceptions
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents errors that occurs requesting a JSON file through HTTP.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Exception" />
|
||||
#if !NETSTANDARD1_3
|
||||
[Serializable]
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Exceptions {
|
||||
/// <summary>
|
||||
/// Represents errors that occurs requesting a JSON file through HTTP.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Exception" />
|
||||
#if !NETSTANDARD1_3
|
||||
[Serializable]
|
||||
#endif
|
||||
public class JsonRequestException
|
||||
: Exception
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:Unosquare.Swan.Exceptions.JsonRequestException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="httpErrorCode">The HTTP error code.</param>
|
||||
/// <param name="errorContent">Content of the error.</param>
|
||||
public JsonRequestException(string message, int httpErrorCode = 500, string errorContent = null)
|
||||
: base(message)
|
||||
{
|
||||
HttpErrorCode = httpErrorCode;
|
||||
HttpErrorContent = errorContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP error code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The HTTP error code.
|
||||
/// </value>
|
||||
public int HttpErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of the HTTP error.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The content of the HTTP error.
|
||||
/// </value>
|
||||
public string HttpErrorContent { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => string.IsNullOrEmpty(HttpErrorContent) ? $"HTTP Response Status Code {HttpErrorCode} Error Message: {HttpErrorContent}" : base.ToString();
|
||||
}
|
||||
public class JsonRequestException
|
||||
: Exception {
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:Unosquare.Swan.Exceptions.JsonRequestException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="httpErrorCode">The HTTP error code.</param>
|
||||
/// <param name="errorContent">Content of the error.</param>
|
||||
public JsonRequestException(String message, Int32 httpErrorCode = 500, String errorContent = null)
|
||||
: base(message) {
|
||||
this.HttpErrorCode = httpErrorCode;
|
||||
this.HttpErrorContent = errorContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP error code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The HTTP error code.
|
||||
/// </value>
|
||||
public Int32 HttpErrorCode {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of the HTTP error.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The content of the HTTP error.
|
||||
/// </value>
|
||||
public String HttpErrorContent {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override String ToString() => String.IsNullOrEmpty(this.HttpErrorContent) ? $"HTTP Response Status Code {this.HttpErrorCode} Error Message: {this.HttpErrorContent}" : base.ToString();
|
||||
}
|
||||
}
|
||||
|
@ -1,134 +1,133 @@
|
||||
namespace Unosquare.Swan.Exceptions
|
||||
{
|
||||
using System;
|
||||
using Networking.Ldap;
|
||||
|
||||
using System;
|
||||
using Unosquare.Swan.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>
|
||||
/// 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.
|
||||
/// Initializes a new instance of the <see cref="LdapException" /> class.
|
||||
/// Constructs an exception with a detailed message obtained from the
|
||||
/// specified <c>MessageOrKey</c> String.
|
||||
/// Additional parameters specify the result code, the message returned
|
||||
/// from the server, and a matchedDN returned from the server.
|
||||
/// 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>
|
||||
/// <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>
|
||||
/// Initializes a new instance of the <see cref="LdapException" /> class.
|
||||
/// Constructs an exception with a detailed message obtained from the
|
||||
/// specified <c>MessageOrKey</c> String.
|
||||
/// Additional parameters specify the result code, the message returned
|
||||
/// from the server, and a matchedDN returned from the server.
|
||||
/// 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>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="resultCode">The result code returned.</param>
|
||||
/// <param name="serverMsg">Error message specifying additional information
|
||||
/// from the server.</param>
|
||||
/// <param name="matchedDN">The maximal subset of a specified DN which could
|
||||
/// be matched by the server on a search operation.</param>
|
||||
/// <param name="rootException">The root exception.</param>
|
||||
public LdapException(
|
||||
string message,
|
||||
LdapStatusCode resultCode,
|
||||
string serverMsg = null,
|
||||
string matchedDN = null,
|
||||
Exception rootException = null)
|
||||
: base(message)
|
||||
{
|
||||
ResultCode = resultCode;
|
||||
Cause = rootException;
|
||||
MatchedDN = matchedDN;
|
||||
_serverMessage = serverMsg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error message from the Ldap server, if this message is
|
||||
/// available (that is, if this message was set). If the message was not set,
|
||||
/// this method returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The error message or null if the message was not set.
|
||||
/// </returns>
|
||||
public string LdapErrorMessage =>
|
||||
_serverMessage != null && _serverMessage.Length == 0 ? null : _serverMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the lower level Exception which caused the failure, if any.
|
||||
/// For example, an IOException with additional information may be returned
|
||||
/// on a CONNECT_ERROR failure.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The cause.
|
||||
/// </value>
|
||||
public Exception Cause { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the result code from the exception.
|
||||
/// The codes are defined as <c>public final static int</c> members
|
||||
/// of the Ldap Exception class. If the exception is a
|
||||
/// result of error information returned from a directory operation, the
|
||||
/// code will be one of those defined for the class. Otherwise, a local error
|
||||
/// code is returned.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The result code.
|
||||
/// </value>
|
||||
public LdapStatusCode ResultCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the part of a submitted distinguished name which could be
|
||||
/// matched by the server.
|
||||
/// If the exception was caused by a local error, such as no server
|
||||
/// available, the return value is null. If the exception resulted from
|
||||
/// an operation being executed on a server, the value is an empty string
|
||||
/// except when the result of the operation was one of the following:.
|
||||
/// <ul><li>NO_SUCH_OBJECT</li><li>ALIAS_PROBLEM</li><li>INVALID_DN_SYNTAX</li><li>ALIAS_DEREFERENCING_PROBLEM</li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The matched dn.
|
||||
/// </value>
|
||||
public string MatchedDN { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Message => ResultCode.ToString().Humanize();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="resultCode">The result code returned.</param>
|
||||
/// <param name="serverMsg">Error message specifying additional information
|
||||
/// from the server.</param>
|
||||
/// <param name="matchedDN">The maximal subset of a specified DN which could
|
||||
/// be matched by the server on a search operation.</param>
|
||||
/// <param name="rootException">The root exception.</param>
|
||||
public LdapException(
|
||||
String message,
|
||||
LdapStatusCode resultCode,
|
||||
String serverMsg = null,
|
||||
String matchedDN = null,
|
||||
Exception rootException = null)
|
||||
: base(message) {
|
||||
this.ResultCode = resultCode;
|
||||
this.Cause = rootException;
|
||||
this.MatchedDN = matchedDN;
|
||||
this._serverMessage = serverMsg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the error message from the Ldap server, if this message is
|
||||
/// available (that is, if this message was set). If the message was not set,
|
||||
/// this method returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The error message or null if the message was not set.
|
||||
/// </returns>
|
||||
public String LdapErrorMessage =>
|
||||
this._serverMessage != null && this._serverMessage.Length == 0 ? null : this._serverMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the lower level Exception which caused the failure, if any.
|
||||
/// For example, an IOException with additional information may be returned
|
||||
/// on a CONNECT_ERROR failure.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The cause.
|
||||
/// </value>
|
||||
public Exception Cause {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the result code from the exception.
|
||||
/// The codes are defined as <c>public final static int</c> members
|
||||
/// of the Ldap Exception class. If the exception is a
|
||||
/// result of error information returned from a directory operation, the
|
||||
/// code will be one of those defined for the class. Otherwise, a local error
|
||||
/// code is returned.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The result code.
|
||||
/// </value>
|
||||
public LdapStatusCode ResultCode {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the part of a submitted distinguished name which could be
|
||||
/// matched by the server.
|
||||
/// If the exception was caused by a local error, such as no server
|
||||
/// available, the return value is null. If the exception resulted from
|
||||
/// an operation being executed on a server, the value is an empty string
|
||||
/// except when the result of the operation was one of the following:.
|
||||
/// <ul><li>NO_SUCH_OBJECT</li><li>ALIAS_PROBLEM</li><li>INVALID_DN_SYNTAX</li><li>ALIAS_DEREFERENCING_PROBLEM</li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The matched dn.
|
||||
/// </value>
|
||||
public String MatchedDN {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override String Message => this.ResultCode.ToString().Humanize();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override String ToString() {
|
||||
// Craft a string from the resource file
|
||||
String msg = $"{nameof(LdapException)}: {base.Message} ({this.ResultCode}) {this.ResultCode.ToString().Humanize()}";
|
||||
|
||||
// Add server message
|
||||
if(!String.IsNullOrEmpty(this._serverMessage)) {
|
||||
msg += $"\r\nServer Message: {this._serverMessage}";
|
||||
}
|
||||
|
||||
// Add Matched DN message
|
||||
if(this.MatchedDN != null) {
|
||||
msg += $"\r\nMatched DN: {this.MatchedDN}";
|
||||
}
|
||||
|
||||
if(this.Cause != null) {
|
||||
msg += $"\r\n{this.Cause}";
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +1,51 @@
|
||||
#if NET452 || NETSTANDARD2_0
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Mail;
|
||||
using System.Reflection;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Mail;
|
||||
using System.Reflection;
|
||||
namespace Unosquare.Swan {
|
||||
/// <summary>
|
||||
/// Extension methods.
|
||||
/// </summary>
|
||||
public static class SmtpExtensions {
|
||||
/// <summary>
|
||||
/// Extension methods.
|
||||
/// The raw contents of this MailMessage as a MemoryStream.
|
||||
/// </summary>
|
||||
public static class SmtpExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The raw contents of this MailMessage as a MemoryStream.
|
||||
/// </summary>
|
||||
/// <param name="self">The caller.</param>
|
||||
/// <returns>A MemoryStream with the raw contents of this MailMessage.</returns>
|
||||
public static MemoryStream ToMimeMessage(this MailMessage self)
|
||||
{
|
||||
if (self == null)
|
||||
throw new ArgumentNullException(nameof(self));
|
||||
|
||||
var result = new MemoryStream();
|
||||
var mailWriter = MimeMessageConstants.MailWriterConstructor.Invoke(new object[] { result });
|
||||
MimeMessageConstants.SendMethod.Invoke(
|
||||
self,
|
||||
MimeMessageConstants.PrivateInstanceFlags,
|
||||
null,
|
||||
MimeMessageConstants.IsRunningInDotNetFourPointFive ? new[] { mailWriter, true, true } : new[] { mailWriter, true },
|
||||
null);
|
||||
|
||||
result = new MemoryStream(result.ToArray());
|
||||
MimeMessageConstants.CloseMethod.Invoke(
|
||||
mailWriter,
|
||||
MimeMessageConstants.PrivateInstanceFlags,
|
||||
null,
|
||||
new object[] { },
|
||||
null);
|
||||
result.Position = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static class MimeMessageConstants
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
/// <param name="self">The caller.</param>
|
||||
/// <returns>A MemoryStream with the raw contents of this MailMessage.</returns>
|
||||
public static MemoryStream ToMimeMessage(this MailMessage self) {
|
||||
if(self == null) {
|
||||
throw new ArgumentNullException(nameof(self));
|
||||
}
|
||||
|
||||
MemoryStream result = new MemoryStream();
|
||||
Object mailWriter = MimeMessageConstants.MailWriterConstructor.Invoke(new Object[] { result });
|
||||
_ = MimeMessageConstants.SendMethod.Invoke(
|
||||
self,
|
||||
MimeMessageConstants.PrivateInstanceFlags,
|
||||
null,
|
||||
MimeMessageConstants.IsRunningInDotNetFourPointFive ? new[] { mailWriter, true, true } : new[] { mailWriter, true },
|
||||
null);
|
||||
|
||||
result = new MemoryStream(result.ToArray());
|
||||
_ = MimeMessageConstants.CloseMethod.Invoke(
|
||||
mailWriter,
|
||||
MimeMessageConstants.PrivateInstanceFlags,
|
||||
null,
|
||||
new Object[] { },
|
||||
null);
|
||||
result.Position = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static class MimeMessageConstants {
|
||||
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 Boolean IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -1,58 +1,58 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
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>
|
||||
/// Provides various extension methods for networking-related tasks.
|
||||
/// Determines whether the IP address is private.
|
||||
/// </summary>
|
||||
public static class NetworkExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the IP address is private.
|
||||
/// </summary>
|
||||
/// <param name="address">The IP address.</param>
|
||||
/// <returns>
|
||||
/// True if the IP Address is private; otherwise, false.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">address.</exception>
|
||||
public static bool IsPrivateAddress(this IPAddress address)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
var octets = address.ToString().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).Select(byte.Parse).ToArray();
|
||||
var is24Bit = octets[0] == 10;
|
||||
var is20Bit = octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31);
|
||||
var is16Bit = octets[0] == 192 && octets[1] == 168;
|
||||
|
||||
return is24Bit || is20Bit || is16Bit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an IPv4 Address to its Unsigned, 32-bit integer representation.
|
||||
/// </summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <returns>
|
||||
/// A 32-bit unsigned integer converted from four bytes at a specified position in a byte array.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">address.</exception>
|
||||
/// <exception cref="ArgumentException">InterNetwork - address.</exception>
|
||||
public static uint ToUInt32(this IPAddress address)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
if (address.AddressFamily != AddressFamily.InterNetwork)
|
||||
throw new ArgumentException($"Address has to be of family '{nameof(AddressFamily.InterNetwork)}'", nameof(address));
|
||||
|
||||
var addressBytes = address.GetAddressBytes();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(addressBytes);
|
||||
|
||||
return BitConverter.ToUInt32(addressBytes, 0);
|
||||
}
|
||||
}
|
||||
/// <param name="address">The IP address.</param>
|
||||
/// <returns>
|
||||
/// True if the IP Address is private; otherwise, false.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">address.</exception>
|
||||
public static Boolean IsPrivateAddress(this IPAddress address) {
|
||||
if(address == null) {
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
}
|
||||
|
||||
Byte[] octets = address.ToString().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).Select(Byte.Parse).ToArray();
|
||||
Boolean is24Bit = octets[0] == 10;
|
||||
Boolean is20Bit = octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31;
|
||||
Boolean is16Bit = octets[0] == 192 && octets[1] == 168;
|
||||
|
||||
return is24Bit || is20Bit || is16Bit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an IPv4 Address to its Unsigned, 32-bit integer representation.
|
||||
/// </summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <returns>
|
||||
/// A 32-bit unsigned integer converted from four bytes at a specified position in a byte array.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">address.</exception>
|
||||
/// <exception cref="ArgumentException">InterNetwork - address.</exception>
|
||||
public static UInt32 ToUInt32(this IPAddress address) {
|
||||
if(address == null) {
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
}
|
||||
|
||||
if(address.AddressFamily != AddressFamily.InterNetwork) {
|
||||
throw new ArgumentException($"Address has to be of family '{nameof(AddressFamily.InterNetwork)}'", nameof(address));
|
||||
}
|
||||
|
||||
Byte[] addressBytes = address.GetAddressBytes();
|
||||
if(BitConverter.IsLittleEndian) {
|
||||
Array.Reverse(addressBytes);
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt32(addressBytes, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +1,75 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
#if NET452
|
||||
using System.ServiceProcess;
|
||||
using System.ServiceProcess;
|
||||
#else
|
||||
using Abstractions;
|
||||
using Abstractions;
|
||||
#endif
|
||||
|
||||
namespace Unosquare.Swan {
|
||||
/// <summary>
|
||||
/// Extension methods.
|
||||
/// </summary>
|
||||
public static class WindowsServicesExtensions {
|
||||
/// <summary>
|
||||
/// Extension methods.
|
||||
/// Runs a service in console mode.
|
||||
/// </summary>
|
||||
public static class WindowsServicesExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs a service in console mode.
|
||||
/// </summary>
|
||||
/// <param name="serviceToRun">The service to run.</param>
|
||||
public static void RunInConsoleMode(this ServiceBase serviceToRun)
|
||||
{
|
||||
if (serviceToRun == null)
|
||||
throw new ArgumentNullException(nameof(serviceToRun));
|
||||
|
||||
RunInConsoleMode(new[] { serviceToRun });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a set of services in console mode.
|
||||
/// </summary>
|
||||
/// <param name="servicesToRun">The services to run.</param>
|
||||
public static void RunInConsoleMode(this ServiceBase[] servicesToRun)
|
||||
{
|
||||
if (servicesToRun == null)
|
||||
throw new ArgumentNullException(nameof(servicesToRun));
|
||||
|
||||
const string onStartMethodName = "OnStart";
|
||||
const string onStopMethodName = "OnStop";
|
||||
|
||||
var onStartMethod = typeof(ServiceBase).GetMethod(onStartMethodName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var onStopMethod = typeof(ServiceBase).GetMethod(onStopMethodName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
var serviceThreads = new List<Thread>();
|
||||
"Starting services . . .".Info(Runtime.EntryAssemblyName.Name);
|
||||
|
||||
foreach (var service in servicesToRun)
|
||||
{
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
onStartMethod.Invoke(service, new object[] { new string[] { } });
|
||||
$"Started service '{service.GetType().Name}'".Info(service.GetType());
|
||||
});
|
||||
|
||||
serviceThreads.Add(thread);
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
"Press any key to stop all services.".Info(Runtime.EntryAssemblyName.Name);
|
||||
Terminal.ReadKey(true, true);
|
||||
"Stopping services . . .".Info(Runtime.EntryAssemblyName.Name);
|
||||
|
||||
foreach (var service in servicesToRun)
|
||||
{
|
||||
onStopMethod.Invoke(service, null);
|
||||
$"Stopped service '{service.GetType().Name}'".Info(service.GetType());
|
||||
}
|
||||
|
||||
foreach (var thread in serviceThreads)
|
||||
thread.Join();
|
||||
|
||||
"Stopped all services.".Info(Runtime.EntryAssemblyName.Name);
|
||||
}
|
||||
}
|
||||
/// <param name="serviceToRun">The service to run.</param>
|
||||
public static void RunInConsoleMode(this ServiceBase serviceToRun) {
|
||||
if(serviceToRun == null) {
|
||||
throw new ArgumentNullException(nameof(serviceToRun));
|
||||
}
|
||||
|
||||
RunInConsoleMode(new[] { serviceToRun });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a set of services in console mode.
|
||||
/// </summary>
|
||||
/// <param name="servicesToRun">The services to run.</param>
|
||||
public static void RunInConsoleMode(this ServiceBase[] servicesToRun) {
|
||||
if(servicesToRun == null) {
|
||||
throw new ArgumentNullException(nameof(servicesToRun));
|
||||
}
|
||||
|
||||
const String onStartMethodName = "OnStart";
|
||||
const String onStopMethodName = "OnStop";
|
||||
|
||||
MethodInfo onStartMethod = typeof(ServiceBase).GetMethod(onStartMethodName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
MethodInfo onStopMethod = typeof(ServiceBase).GetMethod(onStopMethodName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
List<Thread> serviceThreads = new List<Thread>();
|
||||
"Starting services . . .".Info(Runtime.EntryAssemblyName.Name);
|
||||
|
||||
foreach(ServiceBase service in servicesToRun) {
|
||||
Thread thread = new Thread(() => {
|
||||
_ = onStartMethod.Invoke(service, new Object[] { new String[] { } });
|
||||
$"Started service '{service.GetType().Name}'".Info(service.GetType());
|
||||
});
|
||||
|
||||
serviceThreads.Add(thread);
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
"Press any key to stop all services.".Info(Runtime.EntryAssemblyName.Name);
|
||||
_ = Terminal.ReadKey(true, true);
|
||||
"Stopping services . . .".Info(Runtime.EntryAssemblyName.Name);
|
||||
|
||||
foreach(ServiceBase service in servicesToRun) {
|
||||
_ = onStopMethod.Invoke(service, null);
|
||||
$"Stopped service '{service.GetType().Name}'".Info(service.GetType());
|
||||
}
|
||||
|
||||
foreach(Thread thread in serviceThreads) {
|
||||
thread.Join();
|
||||
}
|
||||
|
||||
"Stopped all services.".Info(Runtime.EntryAssemblyName.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,179 +1,184 @@
|
||||
#if NET452
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
#if NET452
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
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>
|
||||
/// 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.
|
||||
/// A constant representing the number of
|
||||
/// bytes per pixel in the pixel data. This is
|
||||
/// always 4 but it is kept here for readability.
|
||||
/// </summary>
|
||||
public class BitmapBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// A constant representing the number of
|
||||
/// bytes per pixel in the pixel data. This is
|
||||
/// always 4 but it is kept here for readability.
|
||||
/// </summary>
|
||||
public const int BytesPerPixel = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The blue byte offset within a pixel offset. This is 0.
|
||||
/// </summary>
|
||||
public const int BOffset = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The green byte offset within a pixel offset. This is 1.
|
||||
/// </summary>
|
||||
public const int GOffset = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The red byte offset within a pixel offset. This is 2.
|
||||
/// </summary>
|
||||
public const int ROffset = 2;
|
||||
|
||||
/// <summary>
|
||||
/// The alpha byte offset within a pixel offset. This is 3.
|
||||
/// </summary>
|
||||
public const int AOffset = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitmapBuffer"/> class.
|
||||
/// Data will not contain left-over stride bytes
|
||||
/// </summary>
|
||||
/// <param name="sourceImage">The source image.</param>
|
||||
public BitmapBuffer(Image sourceImage)
|
||||
{
|
||||
// Acquire or create the source bitmap in a manageable format
|
||||
var disposeSourceBitmap = false;
|
||||
if (!(sourceImage is Bitmap sourceBitmap) || sourceBitmap.PixelFormat != PixelFormat)
|
||||
{
|
||||
sourceBitmap = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat);
|
||||
using (var g = Graphics.FromImage(sourceBitmap))
|
||||
{
|
||||
g.DrawImage(sourceImage, 0, 0);
|
||||
}
|
||||
|
||||
// We created this bitmap. Make sure we clear it from memory
|
||||
disposeSourceBitmap = true;
|
||||
}
|
||||
|
||||
// Lock the bits
|
||||
var sourceDataLocker = sourceBitmap.LockBits(
|
||||
new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
|
||||
ImageLockMode.ReadOnly,
|
||||
sourceBitmap.PixelFormat);
|
||||
|
||||
// Set basic properties
|
||||
ImageWidth = sourceBitmap.Width;
|
||||
ImageHeight = sourceBitmap.Height;
|
||||
LineStride = sourceDataLocker.Stride;
|
||||
|
||||
// State variables
|
||||
LineLength = sourceBitmap.Width * BytesPerPixel; // may or may not be equal to the Stride
|
||||
Data = new byte[LineLength * sourceBitmap.Height];
|
||||
|
||||
// copy line by line in order to ignore the useless left-over stride
|
||||
Parallel.For(0, sourceBitmap.Height, y =>
|
||||
{
|
||||
var sourceAddress = sourceDataLocker.Scan0 + (sourceDataLocker.Stride * y);
|
||||
var targetAddress = y * LineLength;
|
||||
Marshal.Copy(sourceAddress, Data, targetAddress, LineLength);
|
||||
});
|
||||
|
||||
// finally unlock the bitmap
|
||||
sourceBitmap.UnlockBits(sourceDataLocker);
|
||||
|
||||
// dispose the source bitmap if we had to create it
|
||||
if (disposeSourceBitmap)
|
||||
{
|
||||
sourceBitmap.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all the bytes of the pixel data
|
||||
/// Each horizontal scanline is represented by LineLength
|
||||
/// rather than by LinceStride. The left-over stride bytes
|
||||
/// are removed.
|
||||
/// </summary>
|
||||
public byte[] Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the image.
|
||||
/// </summary>
|
||||
public int ImageWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the image.
|
||||
/// </summary>
|
||||
public int ImageHeight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pixel format. This will always be Format32bppArgb.
|
||||
/// </summary>
|
||||
public PixelFormat PixelFormat { get; } = PixelFormat.Format32bppArgb;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public int LineStride { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length in bytes of a line of pixel data.
|
||||
/// Basically the same as Stride except Stride might be a little larger as
|
||||
/// 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>
|
||||
/// <param name="x">The x.</param>
|
||||
/// <param name="y">The y.</param>
|
||||
/// <returns>Index of the first byte in the BGRA pixel.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// x
|
||||
/// or
|
||||
/// y.
|
||||
/// </exception>
|
||||
public int GetPixelOffset(int x, int y)
|
||||
{
|
||||
if (x < 0 || x > ImageWidth) throw new ArgumentOutOfRangeException(nameof(x));
|
||||
if (y < 0 || y > ImageHeight) throw new ArgumentOutOfRangeException(nameof(y));
|
||||
|
||||
return (y * LineLength) + (x * BytesPerPixel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the pixel data bytes held in the buffer
|
||||
/// to a 32-bit RGBA bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Pixel data for a graphics image and its attribute.</returns>
|
||||
public Bitmap ToBitmap()
|
||||
{
|
||||
var bitmap = new Bitmap(ImageWidth, ImageHeight, PixelFormat);
|
||||
var bitLocker = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
|
||||
|
||||
Parallel.For(0, bitmap.Height, y =>
|
||||
{
|
||||
var sourceOffset = GetPixelOffset(0, y);
|
||||
var targetOffset = bitLocker.Scan0 + (y * bitLocker.Stride);
|
||||
Marshal.Copy(Data, sourceOffset, targetOffset, bitLocker.Width);
|
||||
});
|
||||
|
||||
bitmap.UnlockBits(bitLocker);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
public const Int32 BytesPerPixel = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The blue byte offset within a pixel offset. This is 0.
|
||||
/// </summary>
|
||||
public const Int32 BOffset = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The green byte offset within a pixel offset. This is 1.
|
||||
/// </summary>
|
||||
public const Int32 GOffset = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The red byte offset within a pixel offset. This is 2.
|
||||
/// </summary>
|
||||
public const Int32 ROffset = 2;
|
||||
|
||||
/// <summary>
|
||||
/// The alpha byte offset within a pixel offset. This is 3.
|
||||
/// </summary>
|
||||
public const Int32 AOffset = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitmapBuffer"/> class.
|
||||
/// Data will not contain left-over stride bytes
|
||||
/// </summary>
|
||||
/// <param name="sourceImage">The source image.</param>
|
||||
public BitmapBuffer(Image sourceImage) {
|
||||
// Acquire or create the source bitmap in a manageable format
|
||||
Boolean disposeSourceBitmap = false;
|
||||
if(!(sourceImage is Bitmap sourceBitmap) || sourceBitmap.PixelFormat != this.PixelFormat) {
|
||||
sourceBitmap = new Bitmap(sourceImage.Width, sourceImage.Height, this.PixelFormat);
|
||||
using(Graphics g = Graphics.FromImage(sourceBitmap)) {
|
||||
g.DrawImage(sourceImage, 0, 0);
|
||||
}
|
||||
|
||||
// We created this bitmap. Make sure we clear it from memory
|
||||
disposeSourceBitmap = true;
|
||||
}
|
||||
|
||||
// Lock the bits
|
||||
BitmapData sourceDataLocker = sourceBitmap.LockBits(
|
||||
new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
|
||||
ImageLockMode.ReadOnly,
|
||||
sourceBitmap.PixelFormat);
|
||||
|
||||
// Set basic properties
|
||||
this.ImageWidth = sourceBitmap.Width;
|
||||
this.ImageHeight = sourceBitmap.Height;
|
||||
this.LineStride = sourceDataLocker.Stride;
|
||||
|
||||
// State variables
|
||||
this.LineLength = sourceBitmap.Width * BytesPerPixel; // may or may not be equal to the Stride
|
||||
this.Data = new Byte[this.LineLength * sourceBitmap.Height];
|
||||
|
||||
// copy line by line in order to ignore the useless left-over stride
|
||||
_ = Parallel.For(0, sourceBitmap.Height, y => {
|
||||
IntPtr sourceAddress = sourceDataLocker.Scan0 + sourceDataLocker.Stride * y;
|
||||
Int32 targetAddress = y * this.LineLength;
|
||||
Marshal.Copy(sourceAddress, this.Data, targetAddress, this.LineLength);
|
||||
});
|
||||
|
||||
// finally unlock the bitmap
|
||||
sourceBitmap.UnlockBits(sourceDataLocker);
|
||||
|
||||
// dispose the source bitmap if we had to create it
|
||||
if(disposeSourceBitmap) {
|
||||
sourceBitmap.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains all the bytes of the pixel data
|
||||
/// Each horizontal scanline is represented by LineLength
|
||||
/// rather than by LinceStride. The left-over stride bytes
|
||||
/// are removed.
|
||||
/// </summary>
|
||||
public Byte[] Data {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the image.
|
||||
/// </summary>
|
||||
public Int32 ImageWidth {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the image.
|
||||
/// </summary>
|
||||
public Int32 ImageHeight {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pixel format. This will always be Format32bppArgb.
|
||||
/// </summary>
|
||||
public PixelFormat PixelFormat { get; } = PixelFormat.Format32bppArgb;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public Int32 LineStride {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length in bytes of a line of pixel data.
|
||||
/// Basically the same as Stride except Stride might be a little larger as
|
||||
/// some bitmaps might be DWORD-algned.
|
||||
/// </summary>
|
||||
public Int32 LineLength {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the first byte in the BGRA pixel data for the given image coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The x.</param>
|
||||
/// <param name="y">The y.</param>
|
||||
/// <returns>Index of the first byte in the BGRA pixel.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// x
|
||||
/// or
|
||||
/// y.
|
||||
/// </exception>
|
||||
public Int32 GetPixelOffset(Int32 x, Int32 y) {
|
||||
if(x < 0 || x > this.ImageWidth) {
|
||||
throw new ArgumentOutOfRangeException(nameof(x));
|
||||
}
|
||||
|
||||
if(y < 0 || y > this.ImageHeight) {
|
||||
throw new ArgumentOutOfRangeException(nameof(y));
|
||||
}
|
||||
|
||||
return y * this.LineLength + x * BytesPerPixel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the pixel data bytes held in the buffer
|
||||
/// to a 32-bit RGBA bitmap.
|
||||
/// </summary>
|
||||
/// <returns>Pixel data for a graphics image and its attribute.</returns>
|
||||
public Bitmap ToBitmap() {
|
||||
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 => {
|
||||
Int32 sourceOffset = this.GetPixelOffset(0, y);
|
||||
IntPtr targetOffset = bitLocker.Scan0 + y * bitLocker.Stride;
|
||||
Marshal.Copy(this.Data, sourceOffset, targetOffset, bitLocker.Width);
|
||||
});
|
||||
|
||||
bitmap.UnlockBits(bitLocker);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -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>
|
||||
/// Represents a Ok value or Error value.
|
||||
/// Gets or sets a value indicating whether this instance is Ok.
|
||||
/// </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>
|
||||
/// Gets or sets a value indicating whether this instance is Ok.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is ok; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsOk => !Equals(Ok, default(T));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ok.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ok.
|
||||
/// </value>
|
||||
public T Ok { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the error.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The error.
|
||||
/// </value>
|
||||
public TError Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new OkOrError from the specified Ok object.
|
||||
/// </summary>
|
||||
/// <param name="ok">The ok.</param>
|
||||
/// <returns>OkOrError instance.</returns>
|
||||
public static OkOrError<T, TError> FromOk(T ok) => new OkOrError<T, TError> { Ok = ok };
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new OkOrError from the specified Error object.
|
||||
/// </summary>
|
||||
/// <param name="error">The error.</param>
|
||||
/// <returns>OkOrError instance.</returns>
|
||||
public static OkOrError<T, TError> FromError(TError error) => new OkOrError<T, TError> { Error = error };
|
||||
}
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is ok; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsOk => !Equals(this.Ok, default(T));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ok.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ok.
|
||||
/// </value>
|
||||
public T Ok {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the error.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The error.
|
||||
/// </value>
|
||||
public TError Error {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new OkOrError from the specified Ok object.
|
||||
/// </summary>
|
||||
/// <param name="ok">The ok.</param>
|
||||
/// <returns>OkOrError instance.</returns>
|
||||
public static OkOrError<T, TError> FromOk(T ok) => new OkOrError<T, TError> { Ok = ok };
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new OkOrError from the specified Error object.
|
||||
/// </summary>
|
||||
/// <param name="error">The error.</param>
|
||||
/// <returns>OkOrError instance.</returns>
|
||||
public static OkOrError<T, TError> FromError(TError error) => new OkOrError<T, TError> { Error = error };
|
||||
}
|
||||
}
|
||||
|
@ -1,450 +1,414 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Unosquare.Swan.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
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>
|
||||
/// Provides miscellaneous network utilities such as a Public IP finder,
|
||||
/// a DNS client to query DNS records of any kind, and an NTP client.
|
||||
/// The DNS default port.
|
||||
/// </summary>
|
||||
public static class Network
|
||||
{
|
||||
/// <summary>
|
||||
/// The DNS default port.
|
||||
/// </summary>
|
||||
public const int DnsDefaultPort = 53;
|
||||
|
||||
/// <summary>
|
||||
/// The NTP default port.
|
||||
/// </summary>
|
||||
public const int NtpDefaultPort = 123;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the host.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the host.
|
||||
/// </value>
|
||||
public static string HostName => IPGlobalProperties.GetIPGlobalProperties().HostName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the network domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the network domain.
|
||||
/// </value>
|
||||
public static string DomainName => IPGlobalProperties.GetIPGlobalProperties().DomainName;
|
||||
|
||||
#region IP Addresses and Adapters Information Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active IPv4 interfaces.
|
||||
/// Only those interfaces with a valid unicast address and a valid gateway will be returned in the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A collection of NetworkInterface/IPInterfaceProperties pairs
|
||||
/// that represents the active IPv4 interfaces.
|
||||
/// </returns>
|
||||
public static Dictionary<NetworkInterface, IPInterfaceProperties> GetIPv4Interfaces()
|
||||
{
|
||||
// zero conf ip address
|
||||
var zeroConf = new IPAddress(0);
|
||||
|
||||
var adapters = NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(network =>
|
||||
network.OperationalStatus == OperationalStatus.Up
|
||||
&& network.NetworkInterfaceType != NetworkInterfaceType.Unknown
|
||||
&& network.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||
.ToArray();
|
||||
|
||||
var result = new Dictionary<NetworkInterface, IPInterfaceProperties>();
|
||||
|
||||
foreach (var adapter in adapters)
|
||||
{
|
||||
var properties = adapter.GetIPProperties();
|
||||
if (properties == null
|
||||
|| properties.GatewayAddresses.Count == 0
|
||||
|| properties.GatewayAddresses.All(gateway => Equals(gateway.Address, zeroConf))
|
||||
|| properties.UnicastAddresses.Count == 0
|
||||
|| properties.GatewayAddresses.All(address => Equals(address.Address, zeroConf))
|
||||
|| properties.UnicastAddresses.Any(a => a.Address.AddressFamily == AddressFamily.InterNetwork) ==
|
||||
false)
|
||||
continue;
|
||||
|
||||
result[adapter] = properties;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the local ip addresses.
|
||||
/// </summary>
|
||||
/// <param name="includeLoopback">if set to <c>true</c> [include loopback].</param>
|
||||
/// <returns>An array of local ip addresses.</returns>
|
||||
public static IPAddress[] GetIPv4Addresses(bool includeLoopback = true) =>
|
||||
GetIPv4Addresses(NetworkInterfaceType.Unknown, true, includeLoopback);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the local ip addresses.
|
||||
/// </summary>
|
||||
/// <param name="interfaceType">Type of the interface.</param>
|
||||
/// <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>
|
||||
/// <returns>An array of local ip addresses.</returns>
|
||||
public static IPAddress[] GetIPv4Addresses(
|
||||
NetworkInterfaceType interfaceType,
|
||||
bool skipTypeFilter = false,
|
||||
bool includeLoopback = false)
|
||||
{
|
||||
var addressList = new List<IPAddress>();
|
||||
var interfaces = NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(ni =>
|
||||
public const Int32 DnsDefaultPort = 53;
|
||||
|
||||
/// <summary>
|
||||
/// The NTP default port.
|
||||
/// </summary>
|
||||
public const Int32 NtpDefaultPort = 123;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the host.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the host.
|
||||
/// </value>
|
||||
public static String HostName => IPGlobalProperties.GetIPGlobalProperties().HostName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the network domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the network domain.
|
||||
/// </value>
|
||||
public static String DomainName => IPGlobalProperties.GetIPGlobalProperties().DomainName;
|
||||
|
||||
#region IP Addresses and Adapters Information Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active IPv4 interfaces.
|
||||
/// Only those interfaces with a valid unicast address and a valid gateway will be returned in the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A collection of NetworkInterface/IPInterfaceProperties pairs
|
||||
/// that represents the active IPv4 interfaces.
|
||||
/// </returns>
|
||||
public static Dictionary<NetworkInterface, IPInterfaceProperties> GetIPv4Interfaces() {
|
||||
// zero conf ip address
|
||||
IPAddress zeroConf = new IPAddress(0);
|
||||
|
||||
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(network =>
|
||||
network.OperationalStatus == OperationalStatus.Up
|
||||
&& network.NetworkInterfaceType != NetworkInterfaceType.Unknown
|
||||
&& network.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||
.ToArray();
|
||||
|
||||
Dictionary<NetworkInterface, IPInterfaceProperties> result = new Dictionary<NetworkInterface, IPInterfaceProperties>();
|
||||
|
||||
foreach(NetworkInterface adapter in adapters) {
|
||||
IPInterfaceProperties properties = adapter.GetIPProperties();
|
||||
if(properties == null
|
||||
|| properties.GatewayAddresses.Count == 0
|
||||
|| properties.GatewayAddresses.All(gateway => Equals(gateway.Address, zeroConf))
|
||||
|| properties.UnicastAddresses.Count == 0
|
||||
|| properties.GatewayAddresses.All(address => Equals(address.Address, zeroConf))
|
||||
|| properties.UnicastAddresses.Any(a => a.Address.AddressFamily == AddressFamily.InterNetwork) ==
|
||||
false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result[adapter] = properties;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the local ip addresses.
|
||||
/// </summary>
|
||||
/// <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) =>
|
||||
GetIPv4Addresses(NetworkInterfaceType.Unknown, true, includeLoopback);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the local ip addresses.
|
||||
/// </summary>
|
||||
/// <param name="interfaceType">Type of the interface.</param>
|
||||
/// <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>
|
||||
/// <returns>An array of local ip addresses.</returns>
|
||||
public static IPAddress[] GetIPv4Addresses(
|
||||
NetworkInterfaceType interfaceType,
|
||||
Boolean skipTypeFilter = false,
|
||||
Boolean includeLoopback = false) {
|
||||
List<IPAddress> addressList = new List<IPAddress>();
|
||||
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(ni =>
|
||||
#if NET452
|
||||
ni.IsReceiveOnly == false &&
|
||||
ni.IsReceiveOnly == false &&
|
||||
#endif
|
||||
(skipTypeFilter || ni.NetworkInterfaceType == interfaceType) &&
|
||||
ni.OperationalStatus == OperationalStatus.Up)
|
||||
.ToArray();
|
||||
|
||||
foreach (var networkInterface in interfaces)
|
||||
{
|
||||
var properties = networkInterface.GetIPProperties();
|
||||
|
||||
if (properties.GatewayAddresses.All(g => g.Address.AddressFamily != AddressFamily.InterNetwork))
|
||||
continue;
|
||||
|
||||
addressList.AddRange(properties.UnicastAddresses
|
||||
.Where(i => i.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
.Select(i => i.Address));
|
||||
}
|
||||
|
||||
if (includeLoopback || interfaceType == NetworkInterfaceType.Loopback)
|
||||
addressList.Add(IPAddress.Loopback);
|
||||
|
||||
return addressList.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the public IP address using ipify.org.
|
||||
/// </summary>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <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())
|
||||
{
|
||||
var response = await client.GetAsync("https://api.ipify.org", ct).ConfigureAwait(false);
|
||||
return IPAddress.Parse(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the public IP address using ipify.org.
|
||||
/// </summary>
|
||||
/// <returns>A public ip address.</returns>
|
||||
public static IPAddress GetPublicIPAddress() => GetPublicIPAddressAsync().GetAwaiter().GetResult();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured IPv4 DNS servers for the active network interfaces.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A collection of NetworkInterface/IPInterfaceProperties pairs
|
||||
/// that represents the active IPv4 interfaces.
|
||||
/// </returns>
|
||||
public static IPAddress[] GetIPv4DnsServers()
|
||||
=> GetIPv4Interfaces()
|
||||
.Select(a => a.Value.DnsAddresses.Where(d => d.AddressFamily == AddressFamily.InterNetwork))
|
||||
.SelectMany(d => d)
|
||||
.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
#region DNS and NTP Clients
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DNS host entry (a list of IP addresses) for the domain name.
|
||||
/// </summary>
|
||||
/// <param name="fqdn">The FQDN.</param>
|
||||
/// <returns>An array of local ip addresses.</returns>
|
||||
public static IPAddress[] GetDnsHostEntry(string fqdn)
|
||||
{
|
||||
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>
|
||||
/// <param name="fqdn">The FQDN.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>An array of local ip addresses of the result produced by this task.</returns>
|
||||
public static Task<IPAddress[]> GetDnsHostEntryAsync(string fqdn,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.Run(() => GetDnsHostEntry(fqdn), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DNS host entry (a list of IP addresses) for the domain name.
|
||||
/// </summary>
|
||||
/// <param name="fqdn">The FQDN.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>
|
||||
/// An array of local ip addresses.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">fqdn.</exception>
|
||||
public static IPAddress[] GetDnsHostEntry(string fqdn, IPAddress dnsServer, int port)
|
||||
{
|
||||
if (fqdn == null)
|
||||
throw new ArgumentNullException(nameof(fqdn));
|
||||
|
||||
if (fqdn.IndexOf(".", StringComparison.Ordinal) == -1)
|
||||
{
|
||||
fqdn += "." + IPGlobalProperties.GetIPGlobalProperties().DomainName;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (fqdn.EndsWith(".") == false) break;
|
||||
|
||||
fqdn = fqdn.Substring(0, fqdn.Length - 1);
|
||||
}
|
||||
|
||||
var client = new DnsClient(dnsServer, port);
|
||||
var result = client.Lookup(fqdn);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DNS host entry (a list of IP addresses) for the domain name.
|
||||
/// </summary>
|
||||
/// <param name="fqdn">The FQDN.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>An array of local ip addresses of the result produced by this task.</returns>
|
||||
public static Task<IPAddress[]> GetDnsHostEntryAsync(
|
||||
string fqdn,
|
||||
IPAddress dnsServer,
|
||||
int port,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.Run(() => GetDnsHostEntry(fqdn, dnsServer, port), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reverse lookup FQDN of the given IP Address.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static string GetDnsPointerEntry(IPAddress query, IPAddress dnsServer, int port) => new DnsClient(dnsServer, port).Reverse(query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reverse lookup FQDN of the given IP Address.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static Task<string> GetDnsPointerEntryAsync(
|
||||
IPAddress query,
|
||||
IPAddress dnsServer,
|
||||
int port,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.Run(() => GetDnsPointerEntry(query, dnsServer, port), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reverse lookup FQDN of the given IP Address.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static string GetDnsPointerEntry(IPAddress query) => new DnsClient(GetIPv4DnsServers().FirstOrDefault()).Reverse(query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reverse lookup FQDN of the given IP Address.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public static Task<string> GetDnsPointerEntryAsync(
|
||||
IPAddress query,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.Run(() => GetDnsPointerEntry(query), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the DNS server for the specified record type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="recordType">Type of the record.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>
|
||||
/// Appropriate DNS server for the specified record type.
|
||||
/// </returns>
|
||||
public static DnsQueryResult QueryDns(string query, DnsRecordType recordType, IPAddress dnsServer, int port)
|
||||
{
|
||||
if (query == null)
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
|
||||
var response = new DnsClient(dnsServer, port).Resolve(query, recordType);
|
||||
return new DnsQueryResult(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the DNS server for the specified record type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="recordType">Type of the record.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns>
|
||||
public static Task<DnsQueryResult> QueryDnsAsync(
|
||||
string query,
|
||||
DnsRecordType recordType,
|
||||
IPAddress dnsServer,
|
||||
int port,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.Run(() => QueryDns(query, recordType, dnsServer, port), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the DNS server for the specified record type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="recordType">Type of the record.</param>
|
||||
/// <returns>Appropriate DNS server for the specified record type.</returns>
|
||||
public static DnsQueryResult QueryDns(string query, DnsRecordType recordType) => QueryDns(query, recordType, GetIPv4DnsServers().FirstOrDefault(), DnsDefaultPort);
|
||||
|
||||
/// <summary>
|
||||
/// Queries the DNS server for the specified record type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="recordType">Type of the record.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns>
|
||||
public static Task<DnsQueryResult> QueryDnsAsync(
|
||||
string query,
|
||||
DnsRecordType recordType,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.Run(() => QueryDns(query, recordType), ct);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <returns>
|
||||
/// A new instance of the DateTime structure to
|
||||
/// the specified year, month, day, hour, minute and second.
|
||||
/// </returns>
|
||||
public static DateTime GetNetworkTimeUtc(IPAddress ntpServerAddress, int port = NtpDefaultPort)
|
||||
{
|
||||
if (ntpServerAddress == null)
|
||||
throw new ArgumentNullException(nameof(ntpServerAddress));
|
||||
|
||||
// NTP message size - 16 bytes of the digest (RFC 2030)
|
||||
var ntpData = new byte[48];
|
||||
|
||||
// Setting the Leap Indicator, Version Number and Mode values
|
||||
ntpData[0] = 0x1B; // LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
|
||||
|
||||
// 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);
|
||||
|
||||
socket.Connect(endPoint);
|
||||
socket.ReceiveTimeout = 3000; // Stops code hang if NTP is blocked
|
||||
socket.Send(ntpData);
|
||||
socket.Receive(ntpData);
|
||||
socket.Dispose();
|
||||
|
||||
// Offset to get to the "Transmit Timestamp" field (time at which the reply
|
||||
// departed the server for the client, in 64-bit timestamp format."
|
||||
const byte serverReplyTime = 40;
|
||||
|
||||
// Get the seconds part
|
||||
ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
|
||||
|
||||
// Get the seconds fraction
|
||||
ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
|
||||
|
||||
// Convert From big-endian to little-endian to match the platform
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
intPart = intPart.SwapEndianness();
|
||||
fractPart = intPart.SwapEndianness();
|
||||
}
|
||||
|
||||
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
|
||||
|
||||
// The time is given in UTC
|
||||
return new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((long) milliseconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UTC time by querying from an NTP server.
|
||||
/// </summary>
|
||||
/// <param name="ntpServerName">The NTP server, by default pool.ntp.org.</param>
|
||||
/// <param name="port">The port, by default NTP 123.</param>
|
||||
/// <returns>The UTC time by querying from an NTP server.</returns>
|
||||
public static DateTime GetNetworkTimeUtc(string ntpServerName = "pool.ntp.org",
|
||||
int port = NtpDefaultPort)
|
||||
{
|
||||
var addresses = GetDnsHostEntry(ntpServerName);
|
||||
return GetNetworkTimeUtc(addresses.First(), port);
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
(skipTypeFilter || ni.NetworkInterfaceType == interfaceType) &&
|
||||
ni.OperationalStatus == OperationalStatus.Up)
|
||||
.ToArray();
|
||||
|
||||
foreach(NetworkInterface networkInterface in interfaces) {
|
||||
IPInterfaceProperties properties = networkInterface.GetIPProperties();
|
||||
|
||||
if(properties.GatewayAddresses.All(g => g.Address.AddressFamily != AddressFamily.InterNetwork)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addressList.AddRange(properties.UnicastAddresses
|
||||
.Where(i => i.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
.Select(i => i.Address));
|
||||
}
|
||||
|
||||
if(includeLoopback || interfaceType == NetworkInterfaceType.Loopback) {
|
||||
addressList.Add(IPAddress.Loopback);
|
||||
}
|
||||
|
||||
return addressList.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the public IP address using ipify.org.
|
||||
/// </summary>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A public IP address of the result produced by this Task.</returns>
|
||||
public static async Task<IPAddress> GetPublicIPAddressAsync(CancellationToken ct = default) {
|
||||
using(HttpClient client = new HttpClient()) {
|
||||
HttpResponseMessage response = await client.GetAsync("https://api.ipify.org", ct).ConfigureAwait(false);
|
||||
return IPAddress.Parse(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the public IP address using ipify.org.
|
||||
/// </summary>
|
||||
/// <returns>A public ip address.</returns>
|
||||
public static IPAddress GetPublicIPAddress() => GetPublicIPAddressAsync().GetAwaiter().GetResult();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured IPv4 DNS servers for the active network interfaces.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A collection of NetworkInterface/IPInterfaceProperties pairs
|
||||
/// that represents the active IPv4 interfaces.
|
||||
/// </returns>
|
||||
public static IPAddress[] GetIPv4DnsServers()
|
||||
=> GetIPv4Interfaces()
|
||||
.Select(a => a.Value.DnsAddresses.Where(d => d.AddressFamily == AddressFamily.InterNetwork))
|
||||
.SelectMany(d => d)
|
||||
.ToArray();
|
||||
|
||||
#endregion
|
||||
|
||||
#region DNS and NTP Clients
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DNS host entry (a list of IP addresses) for the domain name.
|
||||
/// </summary>
|
||||
/// <param name="fqdn">The FQDN.</param>
|
||||
/// <returns>An array of local ip addresses.</returns>
|
||||
public static IPAddress[] GetDnsHostEntry(String fqdn) {
|
||||
IPAddress 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>
|
||||
/// <param name="fqdn">The FQDN.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>An array of local ip addresses of the result produced by this task.</returns>
|
||||
public static Task<IPAddress[]> GetDnsHostEntryAsync(String fqdn,
|
||||
CancellationToken ct = default) => Task.Run(() => GetDnsHostEntry(fqdn), ct);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DNS host entry (a list of IP addresses) for the domain name.
|
||||
/// </summary>
|
||||
/// <param name="fqdn">The FQDN.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>
|
||||
/// An array of local ip addresses.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">fqdn.</exception>
|
||||
public static IPAddress[] GetDnsHostEntry(String fqdn, IPAddress dnsServer, Int32 port) {
|
||||
if(fqdn == null) {
|
||||
throw new ArgumentNullException(nameof(fqdn));
|
||||
}
|
||||
|
||||
if(fqdn.IndexOf(".", StringComparison.Ordinal) == -1) {
|
||||
fqdn += "." + IPGlobalProperties.GetIPGlobalProperties().DomainName;
|
||||
}
|
||||
|
||||
while(true) {
|
||||
if(fqdn.EndsWith(".") == false) {
|
||||
break;
|
||||
}
|
||||
|
||||
fqdn = fqdn.Substring(0, fqdn.Length - 1);
|
||||
}
|
||||
|
||||
DnsClient client = new DnsClient(dnsServer, port);
|
||||
IList<IPAddress> result = client.Lookup(fqdn);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DNS host entry (a list of IP addresses) for the domain name.
|
||||
/// </summary>
|
||||
/// <param name="fqdn">The FQDN.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>An array of local ip addresses of the result produced by this task.</returns>
|
||||
public static Task<IPAddress[]> GetDnsHostEntryAsync(String fqdn, IPAddress dnsServer, Int32 port, CancellationToken ct = default) => Task.Run(() => GetDnsHostEntry(fqdn, dnsServer, port), ct);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reverse lookup FQDN of the given IP Address.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static String GetDnsPointerEntry(IPAddress query, IPAddress dnsServer, Int32 port) => new DnsClient(dnsServer, port).Reverse(query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reverse lookup FQDN of the given IP Address.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static Task<String> GetDnsPointerEntryAsync(
|
||||
IPAddress query,
|
||||
IPAddress dnsServer,
|
||||
Int32 port,
|
||||
CancellationToken ct = default) => Task.Run(() => GetDnsPointerEntry(query, dnsServer, port), ct);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reverse lookup FQDN of the given IP Address.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static String GetDnsPointerEntry(IPAddress query) => new DnsClient(GetIPv4DnsServers().FirstOrDefault()).Reverse(query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reverse lookup FQDN of the given IP Address.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public static Task<String> GetDnsPointerEntryAsync(
|
||||
IPAddress query,
|
||||
CancellationToken ct = default) => Task.Run(() => GetDnsPointerEntry(query), ct);
|
||||
|
||||
/// <summary>
|
||||
/// Queries the DNS server for the specified record type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="recordType">Type of the record.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>
|
||||
/// Appropriate DNS server for the specified record type.
|
||||
/// </returns>
|
||||
public static DnsQueryResult QueryDns(String query, DnsRecordType recordType, IPAddress dnsServer, Int32 port) {
|
||||
if(query == null) {
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
}
|
||||
|
||||
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>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="recordType">Type of the record.</param>
|
||||
/// <param name="dnsServer">The DNS server.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns>
|
||||
public static Task<DnsQueryResult> QueryDnsAsync(
|
||||
String query,
|
||||
DnsRecordType recordType,
|
||||
IPAddress dnsServer,
|
||||
Int32 port,
|
||||
CancellationToken ct = default) => Task.Run(() => QueryDns(query, recordType, dnsServer, port), ct);
|
||||
|
||||
/// <summary>
|
||||
/// Queries the DNS server for the specified record type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="recordType">Type of the record.</param>
|
||||
/// <returns>Appropriate DNS server for the specified record type.</returns>
|
||||
public static DnsQueryResult QueryDns(String query, DnsRecordType recordType) => QueryDns(query, recordType, GetIPv4DnsServers().FirstOrDefault(), DnsDefaultPort);
|
||||
|
||||
/// <summary>
|
||||
/// Queries the DNS server for the specified record type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="recordType">Type of the record.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns>
|
||||
public static Task<DnsQueryResult> QueryDnsAsync(
|
||||
String query,
|
||||
DnsRecordType recordType,
|
||||
CancellationToken ct = default) => Task.Run(() => QueryDns(query, recordType), ct);
|
||||
|
||||
/// <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>
|
||||
/// <returns>
|
||||
/// A new instance of the DateTime structure to
|
||||
/// the specified year, month, day, hour, minute and second.
|
||||
/// </returns>
|
||||
public static DateTime GetNetworkTimeUtc(IPAddress ntpServerAddress, Int32 port = NtpDefaultPort) {
|
||||
if(ntpServerAddress == null) {
|
||||
throw new ArgumentNullException(nameof(ntpServerAddress));
|
||||
}
|
||||
|
||||
// NTP message size - 16 bytes of the digest (RFC 2030)
|
||||
Byte[] ntpData = new Byte[48];
|
||||
|
||||
// Setting the Leap Indicator, Version Number and Mode values
|
||||
ntpData[0] = 0x1B; // LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
|
||||
|
||||
// The UDP port number assigned to NTP is 123
|
||||
IPEndPoint endPoint = new IPEndPoint(ntpServerAddress, port);
|
||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
|
||||
socket.Connect(endPoint);
|
||||
socket.ReceiveTimeout = 3000; // Stops code hang if NTP is blocked
|
||||
_ = socket.Send(ntpData);
|
||||
_ = socket.Receive(ntpData);
|
||||
socket.Dispose();
|
||||
|
||||
// Offset to get to the "Transmit Timestamp" field (time at which the reply
|
||||
// departed the server for the client, in 64-bit timestamp format."
|
||||
const Byte serverReplyTime = 40;
|
||||
|
||||
// Get the seconds part
|
||||
UInt64 intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
|
||||
|
||||
// Get the seconds fraction
|
||||
UInt64 fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
|
||||
|
||||
// Convert From big-endian to little-endian to match the platform
|
||||
if(BitConverter.IsLittleEndian) {
|
||||
intPart = intPart.SwapEndianness();
|
||||
fractPart = intPart.SwapEndianness();
|
||||
}
|
||||
|
||||
UInt64 milliseconds = intPart * 1000 + fractPart * 1000 / 0x100000000L;
|
||||
|
||||
// The time is given in UTC
|
||||
return new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((Int64)milliseconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UTC time by querying from an NTP server.
|
||||
/// </summary>
|
||||
/// <param name="ntpServerName">The NTP server, by default pool.ntp.org.</param>
|
||||
/// <param name="port">The port, by default NTP 123.</param>
|
||||
/// <returns>The UTC time by querying from an NTP server.</returns>
|
||||
public static DateTime GetNetworkTimeUtc(String ntpServerName = "pool.ntp.org",
|
||||
Int32 port = NtpDefaultPort) {
|
||||
IPAddress[] addresses = GetDnsHostEntry(ntpServerName);
|
||||
return GetNetworkTimeUtc(addresses.First(), port);
|
||||
}
|
||||
|
||||
/// <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,
|
||||
Int32 port = NtpDefaultPort,
|
||||
CancellationToken ct = default) => 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",
|
||||
Int32 port = NtpDefaultPort,
|
||||
CancellationToken ct = default) => Task.Run(() => GetNetworkTimeUtc(ntpServerName, port), ct);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,258 +1,235 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using Swan;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <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>
|
||||
/// TCP Listener manager with built-in events and asynchronous functionality.
|
||||
/// This networking component is typically used when writing server software.
|
||||
/// Occurs when a new connection requests a socket from the listener.
|
||||
/// Set Cancel = true to prevent the TCP client from being accepted.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
public sealed class ConnectionListener : IDisposable
|
||||
{
|
||||
#region Private Declarations
|
||||
|
||||
private readonly object _stateLock = new object();
|
||||
private TcpListener _listenerSocket;
|
||||
private bool _cancellationPending;
|
||||
private CancellationTokenSource _cancelListening;
|
||||
private Task _backgroundWorkerTask;
|
||||
private bool _hasDisposed;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new connection requests a socket from the listener.
|
||||
/// Set Cancel = true to prevent the TCP client from being accepted.
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectionAcceptingEventArgs> OnConnectionAccepting = (s, e) => { };
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new connection is accepted.
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectionAcceptedEventArgs> OnConnectionAccepted = (s, e) => { };
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a connection fails to get accepted
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectionFailureEventArgs> OnConnectionFailure = (s, e) => { };
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the listener stops.
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectionListenerStoppedEventArgs> OnListenerStopped = (s, e) => { };
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="listenEndPoint">The listen end point.</param>
|
||||
public ConnectionListener(IPEndPoint listenEndPoint)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
LocalEndPoint = listenEndPoint ?? throw new ArgumentNullException(nameof(listenEndPoint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionListener"/> class.
|
||||
/// It uses the loopback address for listening.
|
||||
/// </summary>
|
||||
/// <param name="listenPort">The listen port.</param>
|
||||
public ConnectionListener(int listenPort)
|
||||
: this(new IPEndPoint(IPAddress.Loopback, listenPort))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="listenAddress">The listen address.</param>
|
||||
/// <param name="listenPort">The listen port.</param>
|
||||
public ConnectionListener(IPAddress listenAddress, int listenPort)
|
||||
: this(new IPEndPoint(listenAddress, listenPort))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ConnectionListener"/> class.
|
||||
/// </summary>
|
||||
~ConnectionListener()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local end point on which we are listening.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The local end point.
|
||||
/// </value>
|
||||
public IPEndPoint LocalEndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this listener is active.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is listening; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsListening => _backgroundWorkerTask != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique identifier that gets automatically assigned upon instantiation of this class.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The unique identifier.
|
||||
/// </value>
|
||||
public Guid Id { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Start and Stop
|
||||
|
||||
/// <summary>
|
||||
/// Starts the listener in an asynchronous, non-blocking fashion.
|
||||
/// Subscribe to the events of this class to gain access to connected client sockets.
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">Cancellation has already been requested. This listener is not reusable.</exception>
|
||||
public void Start()
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (_backgroundWorkerTask != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_cancellationPending)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cancellation has already been requested. This listener is not reusable.");
|
||||
}
|
||||
|
||||
_backgroundWorkerTask = DoWorkAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the listener from receiving new connections.
|
||||
/// This does not prevent the listener from .
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
_cancellationPending = true;
|
||||
_listenerSocket?.Stop();
|
||||
_cancelListening?.Cancel();
|
||||
_backgroundWorkerTask?.Wait();
|
||||
_backgroundWorkerTask = null;
|
||||
_cancellationPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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() => LocalEndPoint.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_hasDisposed)
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
public event EventHandler<ConnectionAcceptingEventArgs> OnConnectionAccepting = (s, e) => { };
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new connection is accepted.
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectionAcceptedEventArgs> OnConnectionAccepted = (s, e) => { };
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a connection fails to get accepted
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectionFailureEventArgs> OnConnectionFailure = (s, e) => { };
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the listener stops.
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectionListenerStoppedEventArgs> OnListenerStopped = (s, e) => { };
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="listenEndPoint">The listen end point.</param>
|
||||
public ConnectionListener(IPEndPoint listenEndPoint) {
|
||||
this.Id = Guid.NewGuid();
|
||||
this.LocalEndPoint = listenEndPoint ?? throw new ArgumentNullException(nameof(listenEndPoint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionListener"/> class.
|
||||
/// It uses the loopback address for listening.
|
||||
/// </summary>
|
||||
/// <param name="listenPort">The listen port.</param>
|
||||
public ConnectionListener(Int32 listenPort)
|
||||
: this(new IPEndPoint(IPAddress.Loopback, listenPort)) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionListener"/> class.
|
||||
/// </summary>
|
||||
/// <param name="listenAddress">The listen address.</param>
|
||||
/// <param name="listenPort">The listen port.</param>
|
||||
public ConnectionListener(IPAddress listenAddress, Int32 listenPort)
|
||||
: this(new IPEndPoint(listenAddress, listenPort)) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ConnectionListener"/> class.
|
||||
/// </summary>
|
||||
~ConnectionListener() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local end point on which we are listening.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The local end point.
|
||||
/// </value>
|
||||
public IPEndPoint LocalEndPoint {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this listener is active.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is listening; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsListening => this._backgroundWorkerTask != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique identifier that gets automatically assigned upon instantiation of this class.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The unique identifier.
|
||||
/// </value>
|
||||
public Guid Id {
|
||||
get;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Start and Stop
|
||||
|
||||
/// <summary>
|
||||
/// Starts the listener in an asynchronous, non-blocking fashion.
|
||||
/// Subscribe to the events of this class to gain access to connected client sockets.
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">Cancellation has already been requested. This listener is not reusable.</exception>
|
||||
public void Start() {
|
||||
lock(this._stateLock) {
|
||||
if(this._backgroundWorkerTask != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this._cancellationPending) {
|
||||
throw new InvalidOperationException(
|
||||
"Cancellation has already been requested. This listener is not reusable.");
|
||||
}
|
||||
|
||||
this._backgroundWorkerTask = this.DoWorkAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the listener from receiving new connections.
|
||||
/// This does not prevent the listener from .
|
||||
/// </summary>
|
||||
public void Stop() {
|
||||
lock(this._stateLock) {
|
||||
this._cancellationPending = true;
|
||||
this._listenerSocket?.Stop();
|
||||
this._cancelListening?.Cancel();
|
||||
this._backgroundWorkerTask?.Wait();
|
||||
this._backgroundWorkerTask = null;
|
||||
this._cancellationPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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() => this.LocalEndPoint.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
private void Dispose(Boolean disposing) {
|
||||
if(this._hasDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(disposing) {
|
||||
// Release managed resources
|
||||
this.Stop();
|
||||
}
|
||||
|
||||
this._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() {
|
||||
this._cancellationPending = false;
|
||||
this._listenerSocket = new TcpListener(this.LocalEndPoint);
|
||||
this._listenerSocket.Start();
|
||||
this._cancelListening = new CancellationTokenSource();
|
||||
|
||||
try {
|
||||
while(this._cancellationPending == false) {
|
||||
try {
|
||||
TcpClient client = await Task.Run(() => this._listenerSocket.AcceptTcpClientAsync(), this._cancelListening.Token).ConfigureAwait(false);
|
||||
ConnectionAcceptingEventArgs acceptingArgs = new ConnectionAcceptingEventArgs(client);
|
||||
OnConnectionAccepting(this, acceptingArgs);
|
||||
|
||||
if(acceptingArgs.Cancel) {
|
||||
#if !NET452
|
||||
client.Dispose();
|
||||
#else
|
||||
client.Close();
|
||||
client.Close();
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
OnConnectionAccepted(this, new ConnectionAcceptedEventArgs(client));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnConnectionFailure(this, new ConnectionFailureEventArgs(ex));
|
||||
}
|
||||
}
|
||||
|
||||
OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(LocalEndPoint));
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(LocalEndPoint));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnListenerStopped(this,
|
||||
new ConnectionListenerStoppedEventArgs(LocalEndPoint, _cancellationPending ? null : ex));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_backgroundWorkerTask = null;
|
||||
_cancellationPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
OnConnectionAccepted(this, new ConnectionAcceptedEventArgs(client));
|
||||
} catch(Exception ex) {
|
||||
OnConnectionFailure(this, new ConnectionFailureEventArgs(ex));
|
||||
}
|
||||
}
|
||||
|
||||
OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(this.LocalEndPoint));
|
||||
} catch(ObjectDisposedException) {
|
||||
OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(this.LocalEndPoint));
|
||||
} catch(Exception ex) {
|
||||
OnListenerStopped(this,
|
||||
new ConnectionListenerStoppedEventArgs(this.LocalEndPoint, this._cancellationPending ? null : ex));
|
||||
} finally {
|
||||
this._backgroundWorkerTask = null;
|
||||
this._cancellationPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -1,61 +1,95 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// DnsClient public interfaces.
|
||||
/// </summary>
|
||||
internal partial class DnsClient
|
||||
{
|
||||
public interface IDnsMessage
|
||||
{
|
||||
IList<DnsQuestion> Questions { get; }
|
||||
|
||||
int Size { get; }
|
||||
byte[] ToArray();
|
||||
}
|
||||
|
||||
public interface IDnsMessageEntry
|
||||
{
|
||||
DnsDomain Name { get; }
|
||||
DnsRecordType Type { get; }
|
||||
DnsRecordClass Class { get; }
|
||||
|
||||
int Size { get; }
|
||||
byte[] ToArray();
|
||||
}
|
||||
|
||||
public interface IDnsResourceRecord : IDnsMessageEntry
|
||||
{
|
||||
TimeSpan TimeToLive { get; }
|
||||
int DataLength { get; }
|
||||
byte[] Data { get; }
|
||||
}
|
||||
|
||||
public interface IDnsRequest : IDnsMessage
|
||||
{
|
||||
int Id { get; set; }
|
||||
DnsOperationCode OperationCode { get; set; }
|
||||
bool RecursionDesired { get; set; }
|
||||
}
|
||||
|
||||
public interface IDnsResponse : IDnsMessage
|
||||
{
|
||||
int Id { get; set; }
|
||||
IList<IDnsResourceRecord> AnswerRecords { get; }
|
||||
IList<IDnsResourceRecord> AuthorityRecords { get; }
|
||||
IList<IDnsResourceRecord> AdditionalRecords { get; }
|
||||
bool IsRecursionAvailable { get; set; }
|
||||
bool IsAuthorativeServer { get; set; }
|
||||
bool IsTruncated { get; set; }
|
||||
DnsOperationCode OperationCode { get; set; }
|
||||
DnsResponseCode ResponseCode { get; set; }
|
||||
}
|
||||
|
||||
public interface IDnsRequestResolver
|
||||
{
|
||||
DnsClientResponse Request(DnsClientRequest request);
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <summary>
|
||||
/// DnsClient public interfaces.
|
||||
/// </summary>
|
||||
internal partial class DnsClient {
|
||||
public interface IDnsMessage {
|
||||
IList<DnsQuestion> Questions {
|
||||
get;
|
||||
}
|
||||
|
||||
Int32 Size {
|
||||
get;
|
||||
}
|
||||
Byte[] ToArray();
|
||||
}
|
||||
|
||||
public interface IDnsMessageEntry {
|
||||
DnsDomain Name {
|
||||
get;
|
||||
}
|
||||
DnsRecordType Type {
|
||||
get;
|
||||
}
|
||||
DnsRecordClass Class {
|
||||
get;
|
||||
}
|
||||
|
||||
Int32 Size {
|
||||
get;
|
||||
}
|
||||
Byte[] ToArray();
|
||||
}
|
||||
|
||||
public interface IDnsResourceRecord : IDnsMessageEntry {
|
||||
TimeSpan TimeToLive {
|
||||
get;
|
||||
}
|
||||
Int32 DataLength {
|
||||
get;
|
||||
}
|
||||
Byte[] Data {
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDnsRequest : IDnsMessage {
|
||||
Int32 Id {
|
||||
get; set;
|
||||
}
|
||||
DnsOperationCode OperationCode {
|
||||
get; set;
|
||||
}
|
||||
Boolean RecursionDesired {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDnsResponse : IDnsMessage {
|
||||
Int32 Id {
|
||||
get; set;
|
||||
}
|
||||
IList<IDnsResourceRecord> AnswerRecords {
|
||||
get;
|
||||
}
|
||||
IList<IDnsResourceRecord> AuthorityRecords {
|
||||
get;
|
||||
}
|
||||
IList<IDnsResourceRecord> AdditionalRecords {
|
||||
get;
|
||||
}
|
||||
Boolean IsRecursionAvailable {
|
||||
get; set;
|
||||
}
|
||||
Boolean IsAuthorativeServer {
|
||||
get; set;
|
||||
}
|
||||
Boolean IsTruncated {
|
||||
get; set;
|
||||
}
|
||||
DnsOperationCode OperationCode {
|
||||
get; set;
|
||||
}
|
||||
DnsResponseCode ResponseCode {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDnsRequestResolver {
|
||||
DnsClientResponse Request(DnsClientRequest request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,460 +1,432 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using Attributes;
|
||||
using Formatters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// DnsClient public methods.
|
||||
/// </summary>
|
||||
internal partial class DnsClient
|
||||
{
|
||||
public abstract class DnsResourceRecordBase : IDnsResourceRecord
|
||||
{
|
||||
private readonly IDnsResourceRecord _record;
|
||||
|
||||
protected DnsResourceRecordBase(IDnsResourceRecord record)
|
||||
{
|
||||
_record = record;
|
||||
}
|
||||
|
||||
public DnsDomain Name => _record.Name;
|
||||
|
||||
public DnsRecordType Type => _record.Type;
|
||||
|
||||
public DnsRecordClass Class => _record.Class;
|
||||
|
||||
public TimeSpan TimeToLive => _record.TimeToLive;
|
||||
|
||||
public int DataLength => _record.DataLength;
|
||||
|
||||
public byte[] Data => _record.Data;
|
||||
|
||||
public int Size => _record.Size;
|
||||
|
||||
protected virtual string[] IncludedProperties
|
||||
=> new[] {nameof(Name), nameof(Type), nameof(Class), nameof(TimeToLive), nameof(DataLength)};
|
||||
|
||||
public byte[] ToArray() => _record.ToArray();
|
||||
|
||||
public override string ToString()
|
||||
=> Json.SerializeOnly(this, true, IncludedProperties);
|
||||
}
|
||||
|
||||
public class DnsResourceRecord : IDnsResourceRecord
|
||||
{
|
||||
public DnsResourceRecord(
|
||||
DnsDomain domain,
|
||||
byte[] data,
|
||||
DnsRecordType type,
|
||||
DnsRecordClass klass = DnsRecordClass.IN,
|
||||
TimeSpan ttl = default)
|
||||
{
|
||||
Name = domain;
|
||||
Type = type;
|
||||
Class = klass;
|
||||
TimeToLive = ttl;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public DnsDomain Name { get; }
|
||||
|
||||
public DnsRecordType Type { get; }
|
||||
|
||||
public DnsRecordClass Class { get; }
|
||||
|
||||
public TimeSpan TimeToLive { get; }
|
||||
|
||||
public int DataLength => Data.Length;
|
||||
|
||||
public byte[] Data { get; }
|
||||
|
||||
public int Size => Name.Size + Tail.SIZE + Data.Length;
|
||||
|
||||
public static IList<DnsResourceRecord> GetAllFromArray(
|
||||
byte[] message,
|
||||
int offset,
|
||||
int count,
|
||||
out int endOffset)
|
||||
{
|
||||
IList<DnsResourceRecord> records = new List<DnsResourceRecord>(count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
records.Add(FromArray(message, offset, out offset));
|
||||
}
|
||||
|
||||
endOffset = offset;
|
||||
return records;
|
||||
}
|
||||
|
||||
public static DnsResourceRecord FromArray(byte[] message, int offset, out int endOffset)
|
||||
{
|
||||
var domain = DnsDomain.FromArray(message, offset, out offset);
|
||||
var tail = message.ToStruct<Tail>(offset, Tail.SIZE);
|
||||
|
||||
var data = new byte[tail.DataLength];
|
||||
|
||||
offset += Tail.SIZE;
|
||||
Array.Copy(message, offset, data, 0, data.Length);
|
||||
|
||||
endOffset = offset + data.Length;
|
||||
|
||||
return new DnsResourceRecord(domain, data, tail.Type, tail.Class, tail.TimeToLive);
|
||||
}
|
||||
|
||||
public byte[] ToArray()
|
||||
{
|
||||
return new MemoryStream(Size)
|
||||
.Append(Name.ToArray())
|
||||
.Append(new Tail()
|
||||
{
|
||||
Type = Type,
|
||||
Class = Class,
|
||||
TimeToLive = TimeToLive,
|
||||
DataLength = Data.Length,
|
||||
}.ToBytes())
|
||||
.Append(Data)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Json.SerializeOnly(
|
||||
this,
|
||||
true,
|
||||
nameof(Name),
|
||||
nameof(Type),
|
||||
nameof(Class),
|
||||
nameof(TimeToLive),
|
||||
nameof(DataLength));
|
||||
}
|
||||
|
||||
[StructEndianness(Endianness.Big)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
||||
private struct Tail
|
||||
{
|
||||
public const int SIZE = 10;
|
||||
|
||||
private ushort type;
|
||||
private ushort klass;
|
||||
private uint ttl;
|
||||
private ushort dataLength;
|
||||
|
||||
public DnsRecordType Type
|
||||
{
|
||||
get => (DnsRecordType) type;
|
||||
set => type = (ushort) value;
|
||||
}
|
||||
|
||||
public DnsRecordClass Class
|
||||
{
|
||||
get => (DnsRecordClass) klass;
|
||||
set => klass = (ushort) value;
|
||||
}
|
||||
|
||||
public TimeSpan TimeToLive
|
||||
{
|
||||
get => TimeSpan.FromSeconds(ttl);
|
||||
set => ttl = (uint) value.TotalSeconds;
|
||||
}
|
||||
|
||||
public int DataLength
|
||||
{
|
||||
get => dataLength;
|
||||
set => dataLength = (ushort) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsPointerResourceRecord : DnsResourceRecordBase
|
||||
{
|
||||
public DnsPointerResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset)
|
||||
: base(record)
|
||||
{
|
||||
PointerDomainName = DnsDomain.FromArray(message, dataOffset);
|
||||
}
|
||||
|
||||
public DnsDomain PointerDomainName { get; }
|
||||
|
||||
protected override string[] IncludedProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
var temp = new List<string>(base.IncludedProperties) {nameof(PointerDomainName)};
|
||||
return temp.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsIPAddressResourceRecord : DnsResourceRecordBase
|
||||
{
|
||||
public DnsIPAddressResourceRecord(IDnsResourceRecord record)
|
||||
: base(record)
|
||||
{
|
||||
IPAddress = new IPAddress(Data);
|
||||
}
|
||||
|
||||
public IPAddress IPAddress { get; }
|
||||
|
||||
protected override string[] IncludedProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
var temp = new List<string>(base.IncludedProperties) {nameof(IPAddress)};
|
||||
return temp.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsNameServerResourceRecord : DnsResourceRecordBase
|
||||
{
|
||||
public DnsNameServerResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset)
|
||||
: base(record)
|
||||
{
|
||||
NSDomainName = DnsDomain.FromArray(message, dataOffset);
|
||||
}
|
||||
|
||||
public DnsDomain NSDomainName { get; }
|
||||
|
||||
protected override string[] IncludedProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
var temp = new List<string>(base.IncludedProperties) {nameof(NSDomainName)};
|
||||
return temp.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsCanonicalNameResourceRecord : DnsResourceRecordBase
|
||||
{
|
||||
public DnsCanonicalNameResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset)
|
||||
: base(record)
|
||||
{
|
||||
CanonicalDomainName = DnsDomain.FromArray(message, dataOffset);
|
||||
}
|
||||
|
||||
public DnsDomain CanonicalDomainName { get; }
|
||||
|
||||
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties)
|
||||
{
|
||||
nameof(CanonicalDomainName),
|
||||
}.ToArray();
|
||||
}
|
||||
|
||||
public class DnsMailExchangeResourceRecord : DnsResourceRecordBase
|
||||
{
|
||||
private const int PreferenceSize = 2;
|
||||
|
||||
public DnsMailExchangeResourceRecord(
|
||||
IDnsResourceRecord record,
|
||||
byte[] message,
|
||||
int dataOffset)
|
||||
: base(record)
|
||||
{
|
||||
var preference = new byte[PreferenceSize];
|
||||
Array.Copy(message, dataOffset, preference, 0, preference.Length);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(preference);
|
||||
}
|
||||
|
||||
dataOffset += PreferenceSize;
|
||||
|
||||
Preference = BitConverter.ToUInt16(preference, 0);
|
||||
ExchangeDomainName = DnsDomain.FromArray(message, dataOffset);
|
||||
}
|
||||
|
||||
public int Preference { get; }
|
||||
|
||||
public DnsDomain ExchangeDomainName { get; }
|
||||
|
||||
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties)
|
||||
{
|
||||
nameof(Preference),
|
||||
nameof(ExchangeDomainName),
|
||||
}.ToArray();
|
||||
}
|
||||
|
||||
public class DnsStartOfAuthorityResourceRecord : DnsResourceRecordBase
|
||||
{
|
||||
public DnsStartOfAuthorityResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset)
|
||||
: base(record)
|
||||
{
|
||||
MasterDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
|
||||
ResponsibleDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
|
||||
|
||||
var tail = message.ToStruct<Options>(dataOffset, Options.SIZE);
|
||||
|
||||
SerialNumber = tail.SerialNumber;
|
||||
RefreshInterval = tail.RefreshInterval;
|
||||
RetryInterval = tail.RetryInterval;
|
||||
ExpireInterval = tail.ExpireInterval;
|
||||
MinimumTimeToLive = tail.MinimumTimeToLive;
|
||||
}
|
||||
|
||||
public DnsStartOfAuthorityResourceRecord(
|
||||
DnsDomain domain,
|
||||
DnsDomain master,
|
||||
DnsDomain responsible,
|
||||
long serial,
|
||||
TimeSpan refresh,
|
||||
TimeSpan retry,
|
||||
TimeSpan expire,
|
||||
TimeSpan minTtl,
|
||||
TimeSpan ttl = default)
|
||||
: base(Create(domain, master, responsible, serial, refresh, retry, expire, minTtl, ttl))
|
||||
{
|
||||
MasterDomainName = master;
|
||||
ResponsibleDomainName = responsible;
|
||||
|
||||
SerialNumber = serial;
|
||||
RefreshInterval = refresh;
|
||||
RetryInterval = retry;
|
||||
ExpireInterval = expire;
|
||||
MinimumTimeToLive = minTtl;
|
||||
}
|
||||
|
||||
public DnsDomain MasterDomainName { get; }
|
||||
|
||||
public DnsDomain ResponsibleDomainName { get; }
|
||||
|
||||
public long SerialNumber { get; }
|
||||
|
||||
public TimeSpan RefreshInterval { get; }
|
||||
|
||||
public TimeSpan RetryInterval { get; }
|
||||
|
||||
public TimeSpan ExpireInterval { get; }
|
||||
|
||||
public TimeSpan MinimumTimeToLive { get; }
|
||||
|
||||
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties)
|
||||
{
|
||||
nameof(MasterDomainName),
|
||||
nameof(ResponsibleDomainName),
|
||||
nameof(SerialNumber),
|
||||
}.ToArray();
|
||||
|
||||
private static IDnsResourceRecord Create(
|
||||
DnsDomain domain,
|
||||
DnsDomain master,
|
||||
DnsDomain responsible,
|
||||
long serial,
|
||||
TimeSpan refresh,
|
||||
TimeSpan retry,
|
||||
TimeSpan expire,
|
||||
TimeSpan minTtl,
|
||||
TimeSpan ttl)
|
||||
{
|
||||
var data = new MemoryStream(Options.SIZE + master.Size + responsible.Size);
|
||||
var tail = new Options
|
||||
{
|
||||
SerialNumber = serial,
|
||||
RefreshInterval = refresh,
|
||||
RetryInterval = retry,
|
||||
ExpireInterval = expire,
|
||||
MinimumTimeToLive = minTtl,
|
||||
};
|
||||
|
||||
data.Append(master.ToArray()).Append(responsible.ToArray()).Append(tail.ToBytes());
|
||||
|
||||
return new DnsResourceRecord(domain, data.ToArray(), DnsRecordType.SOA, DnsRecordClass.IN, ttl);
|
||||
}
|
||||
|
||||
[StructEndianness(Endianness.Big)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct Options
|
||||
{
|
||||
public const int SIZE = 20;
|
||||
|
||||
private uint serialNumber;
|
||||
private uint refreshInterval;
|
||||
private uint retryInterval;
|
||||
private uint expireInterval;
|
||||
private uint ttl;
|
||||
|
||||
public long SerialNumber
|
||||
{
|
||||
get => serialNumber;
|
||||
set => serialNumber = (uint) value;
|
||||
}
|
||||
|
||||
public TimeSpan RefreshInterval
|
||||
{
|
||||
get => TimeSpan.FromSeconds(refreshInterval);
|
||||
set => refreshInterval = (uint) value.TotalSeconds;
|
||||
}
|
||||
|
||||
public TimeSpan RetryInterval
|
||||
{
|
||||
get => TimeSpan.FromSeconds(retryInterval);
|
||||
set => retryInterval = (uint) value.TotalSeconds;
|
||||
}
|
||||
|
||||
public TimeSpan ExpireInterval
|
||||
{
|
||||
get => TimeSpan.FromSeconds(expireInterval);
|
||||
set => expireInterval = (uint) value.TotalSeconds;
|
||||
}
|
||||
|
||||
public TimeSpan MinimumTimeToLive
|
||||
{
|
||||
get => TimeSpan.FromSeconds(ttl);
|
||||
set => ttl = (uint) value.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DnsResourceRecordFactory
|
||||
{
|
||||
public static IList<IDnsResourceRecord> GetAllFromArray(
|
||||
byte[] message,
|
||||
int offset,
|
||||
int count,
|
||||
out int endOffset)
|
||||
{
|
||||
var result = new List<IDnsResourceRecord>(count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
result.Add(GetFromArray(message, offset, out offset));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using Unosquare.Swan.Attributes;
|
||||
using Unosquare.Swan.Formatters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <summary>
|
||||
/// DnsClient public methods.
|
||||
/// </summary>
|
||||
internal partial class DnsClient {
|
||||
public abstract class DnsResourceRecordBase : IDnsResourceRecord {
|
||||
private readonly IDnsResourceRecord _record;
|
||||
|
||||
protected DnsResourceRecordBase(IDnsResourceRecord record) => this._record = record;
|
||||
|
||||
public DnsDomain Name => this._record.Name;
|
||||
|
||||
public DnsRecordType Type => this._record.Type;
|
||||
|
||||
public DnsRecordClass Class => this._record.Class;
|
||||
|
||||
public TimeSpan TimeToLive => this._record.TimeToLive;
|
||||
|
||||
public Int32 DataLength => this._record.DataLength;
|
||||
|
||||
public Byte[] Data => this._record.Data;
|
||||
|
||||
public Int32 Size => this._record.Size;
|
||||
|
||||
protected virtual String[] IncludedProperties
|
||||
=> new[] { nameof(this.Name), nameof(this.Type), nameof(this.Class), nameof(this.TimeToLive), nameof(this.DataLength) };
|
||||
|
||||
public Byte[] ToArray() => this._record.ToArray();
|
||||
|
||||
public override String ToString()
|
||||
=> Json.SerializeOnly(this, true, this.IncludedProperties);
|
||||
}
|
||||
|
||||
public class DnsResourceRecord : IDnsResourceRecord {
|
||||
public DnsResourceRecord(
|
||||
DnsDomain domain,
|
||||
Byte[] data,
|
||||
DnsRecordType type,
|
||||
DnsRecordClass klass = DnsRecordClass.IN,
|
||||
TimeSpan ttl = default) {
|
||||
this.Name = domain;
|
||||
this.Type = type;
|
||||
this.Class = klass;
|
||||
this.TimeToLive = ttl;
|
||||
this.Data = data;
|
||||
}
|
||||
|
||||
public DnsDomain Name {
|
||||
get;
|
||||
}
|
||||
|
||||
public DnsRecordType Type {
|
||||
get;
|
||||
}
|
||||
|
||||
public DnsRecordClass Class {
|
||||
get;
|
||||
}
|
||||
|
||||
public TimeSpan TimeToLive {
|
||||
get;
|
||||
}
|
||||
|
||||
public Int32 DataLength => this.Data.Length;
|
||||
|
||||
public Byte[] Data {
|
||||
get;
|
||||
}
|
||||
|
||||
public Int32 Size => this.Name.Size + Tail.SIZE + this.Data.Length;
|
||||
|
||||
public static IList<DnsResourceRecord> GetAllFromArray(
|
||||
Byte[] message,
|
||||
Int32 offset,
|
||||
Int32 count,
|
||||
out Int32 endOffset) {
|
||||
IList<DnsResourceRecord> records = new List<DnsResourceRecord>(count);
|
||||
|
||||
for(Int32 i = 0; i < count; i++) {
|
||||
records.Add(FromArray(message, offset, out offset));
|
||||
}
|
||||
|
||||
endOffset = offset;
|
||||
return records;
|
||||
}
|
||||
|
||||
public static DnsResourceRecord FromArray(Byte[] message, Int32 offset, out Int32 endOffset) {
|
||||
DnsDomain domain = DnsDomain.FromArray(message, offset, out offset);
|
||||
Tail tail = message.ToStruct<Tail>(offset, Tail.SIZE);
|
||||
|
||||
Byte[] data = new Byte[tail.DataLength];
|
||||
|
||||
offset += Tail.SIZE;
|
||||
Array.Copy(message, offset, data, 0, data.Length);
|
||||
|
||||
endOffset = offset + data.Length;
|
||||
|
||||
return new DnsResourceRecord(domain, data, tail.Type, tail.Class, tail.TimeToLive);
|
||||
}
|
||||
|
||||
public Byte[] ToArray() => new MemoryStream(this.Size)
|
||||
.Append(this.Name.ToArray())
|
||||
.Append(new Tail() {
|
||||
Type = Type,
|
||||
Class = Class,
|
||||
TimeToLive = TimeToLive,
|
||||
DataLength = this.Data.Length,
|
||||
}.ToBytes())
|
||||
.Append(this.Data)
|
||||
.ToArray();
|
||||
|
||||
public override String ToString() => Json.SerializeOnly(
|
||||
this,
|
||||
true,
|
||||
nameof(this.Name),
|
||||
nameof(this.Type),
|
||||
nameof(this.Class),
|
||||
nameof(this.TimeToLive),
|
||||
nameof(this.DataLength));
|
||||
|
||||
[StructEndianness(Endianness.Big)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
||||
private struct Tail {
|
||||
public const Int32 SIZE = 10;
|
||||
|
||||
private UInt16 type;
|
||||
private UInt16 klass;
|
||||
private UInt32 ttl;
|
||||
private UInt16 dataLength;
|
||||
|
||||
public DnsRecordType Type {
|
||||
get => (DnsRecordType)this.type;
|
||||
set => this.type = (UInt16)value;
|
||||
}
|
||||
|
||||
public DnsRecordClass Class {
|
||||
get => (DnsRecordClass)this.klass;
|
||||
set => this.klass = (UInt16)value;
|
||||
}
|
||||
|
||||
public TimeSpan TimeToLive {
|
||||
get => TimeSpan.FromSeconds(this.ttl);
|
||||
set => this.ttl = (UInt32)value.TotalSeconds;
|
||||
}
|
||||
|
||||
public Int32 DataLength {
|
||||
get => this.dataLength;
|
||||
set => this.dataLength = (UInt16)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsPointerResourceRecord : DnsResourceRecordBase {
|
||||
public DnsPointerResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
|
||||
: base(record) => this.PointerDomainName = DnsDomain.FromArray(message, dataOffset);
|
||||
|
||||
public DnsDomain PointerDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
protected override String[] IncludedProperties {
|
||||
get {
|
||||
List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.PointerDomainName) };
|
||||
return temp.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsIPAddressResourceRecord : DnsResourceRecordBase {
|
||||
public DnsIPAddressResourceRecord(IDnsResourceRecord record)
|
||||
: base(record) => this.IPAddress = new IPAddress(this.Data);
|
||||
|
||||
public IPAddress IPAddress {
|
||||
get;
|
||||
}
|
||||
|
||||
protected override String[] IncludedProperties {
|
||||
get {
|
||||
List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.IPAddress) };
|
||||
return temp.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsNameServerResourceRecord : DnsResourceRecordBase {
|
||||
public DnsNameServerResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
|
||||
: base(record) => this.NSDomainName = DnsDomain.FromArray(message, dataOffset);
|
||||
|
||||
public DnsDomain NSDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
protected override String[] IncludedProperties {
|
||||
get {
|
||||
List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.NSDomainName) };
|
||||
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 {
|
||||
get;
|
||||
}
|
||||
|
||||
protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
|
||||
{
|
||||
nameof(this.CanonicalDomainName),
|
||||
}.ToArray();
|
||||
}
|
||||
|
||||
public class DnsMailExchangeResourceRecord : DnsResourceRecordBase {
|
||||
private const Int32 PreferenceSize = 2;
|
||||
|
||||
public DnsMailExchangeResourceRecord(
|
||||
IDnsResourceRecord record,
|
||||
Byte[] message,
|
||||
Int32 dataOffset)
|
||||
: base(record) {
|
||||
Byte[] preference = new Byte[PreferenceSize];
|
||||
Array.Copy(message, dataOffset, preference, 0, preference.Length);
|
||||
|
||||
if(BitConverter.IsLittleEndian) {
|
||||
Array.Reverse(preference);
|
||||
}
|
||||
|
||||
dataOffset += PreferenceSize;
|
||||
|
||||
this.Preference = BitConverter.ToUInt16(preference, 0);
|
||||
this.ExchangeDomainName = DnsDomain.FromArray(message, dataOffset);
|
||||
}
|
||||
|
||||
public Int32 Preference {
|
||||
get;
|
||||
}
|
||||
|
||||
public DnsDomain ExchangeDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
|
||||
{
|
||||
nameof(this.Preference),
|
||||
nameof(this.ExchangeDomainName),
|
||||
}.ToArray();
|
||||
}
|
||||
|
||||
public class DnsStartOfAuthorityResourceRecord : DnsResourceRecordBase {
|
||||
public DnsStartOfAuthorityResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
|
||||
: base(record) {
|
||||
this.MasterDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
|
||||
this.ResponsibleDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
|
||||
|
||||
Options tail = message.ToStruct<Options>(dataOffset, Options.SIZE);
|
||||
|
||||
this.SerialNumber = tail.SerialNumber;
|
||||
this.RefreshInterval = tail.RefreshInterval;
|
||||
this.RetryInterval = tail.RetryInterval;
|
||||
this.ExpireInterval = tail.ExpireInterval;
|
||||
this.MinimumTimeToLive = tail.MinimumTimeToLive;
|
||||
}
|
||||
|
||||
public DnsStartOfAuthorityResourceRecord(
|
||||
DnsDomain domain,
|
||||
DnsDomain master,
|
||||
DnsDomain responsible,
|
||||
Int64 serial,
|
||||
TimeSpan refresh,
|
||||
TimeSpan retry,
|
||||
TimeSpan expire,
|
||||
TimeSpan minTtl,
|
||||
TimeSpan ttl = default)
|
||||
: base(Create(domain, master, responsible, serial, refresh, retry, expire, minTtl, ttl)) {
|
||||
this.MasterDomainName = master;
|
||||
this.ResponsibleDomainName = responsible;
|
||||
|
||||
this.SerialNumber = serial;
|
||||
this.RefreshInterval = refresh;
|
||||
this.RetryInterval = retry;
|
||||
this.ExpireInterval = expire;
|
||||
this.MinimumTimeToLive = minTtl;
|
||||
}
|
||||
|
||||
public DnsDomain MasterDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
public DnsDomain ResponsibleDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
public Int64 SerialNumber {
|
||||
get;
|
||||
}
|
||||
|
||||
public TimeSpan RefreshInterval {
|
||||
get;
|
||||
}
|
||||
|
||||
public TimeSpan RetryInterval {
|
||||
get;
|
||||
}
|
||||
|
||||
public TimeSpan ExpireInterval {
|
||||
get;
|
||||
}
|
||||
|
||||
public TimeSpan MinimumTimeToLive {
|
||||
get;
|
||||
}
|
||||
|
||||
protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
|
||||
{
|
||||
nameof(this.MasterDomainName),
|
||||
nameof(this.ResponsibleDomainName),
|
||||
nameof(this.SerialNumber),
|
||||
}.ToArray();
|
||||
|
||||
private static IDnsResourceRecord Create(
|
||||
DnsDomain domain,
|
||||
DnsDomain master,
|
||||
DnsDomain responsible,
|
||||
Int64 serial,
|
||||
TimeSpan refresh,
|
||||
TimeSpan retry,
|
||||
TimeSpan expire,
|
||||
TimeSpan minTtl,
|
||||
TimeSpan ttl) {
|
||||
MemoryStream data = new MemoryStream(Options.SIZE + master.Size + responsible.Size);
|
||||
Options tail = new Options {
|
||||
SerialNumber = serial,
|
||||
RefreshInterval = refresh,
|
||||
RetryInterval = retry,
|
||||
ExpireInterval = expire,
|
||||
MinimumTimeToLive = minTtl,
|
||||
};
|
||||
|
||||
_ = data.Append(master.ToArray()).Append(responsible.ToArray()).Append(tail.ToBytes());
|
||||
|
||||
return new DnsResourceRecord(domain, data.ToArray(), DnsRecordType.SOA, DnsRecordClass.IN, ttl);
|
||||
}
|
||||
|
||||
[StructEndianness(Endianness.Big)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct Options {
|
||||
public const Int32 SIZE = 20;
|
||||
|
||||
private UInt32 serialNumber;
|
||||
private UInt32 refreshInterval;
|
||||
private UInt32 retryInterval;
|
||||
private UInt32 expireInterval;
|
||||
private UInt32 ttl;
|
||||
|
||||
public Int64 SerialNumber {
|
||||
get => this.serialNumber;
|
||||
set => this.serialNumber = (UInt32)value;
|
||||
}
|
||||
|
||||
public TimeSpan RefreshInterval {
|
||||
get => TimeSpan.FromSeconds(this.refreshInterval);
|
||||
set => this.refreshInterval = (UInt32)value.TotalSeconds;
|
||||
}
|
||||
|
||||
public TimeSpan RetryInterval {
|
||||
get => TimeSpan.FromSeconds(this.retryInterval);
|
||||
set => this.retryInterval = (UInt32)value.TotalSeconds;
|
||||
}
|
||||
|
||||
public TimeSpan ExpireInterval {
|
||||
get => TimeSpan.FromSeconds(this.expireInterval);
|
||||
set => this.expireInterval = (UInt32)value.TotalSeconds;
|
||||
}
|
||||
|
||||
public TimeSpan MinimumTimeToLive {
|
||||
get => TimeSpan.FromSeconds(this.ttl);
|
||||
set => this.ttl = (UInt32)value.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DnsResourceRecordFactory {
|
||||
public static IList<IDnsResourceRecord> GetAllFromArray(
|
||||
Byte[] message,
|
||||
Int32 offset,
|
||||
Int32 count,
|
||||
out Int32 endOffset) {
|
||||
List<IDnsResourceRecord> result = new List<IDnsResourceRecord>(count);
|
||||
|
||||
for(Int32 i = 0; i < count; i++) {
|
||||
result.Add(GetFromArray(message, offset, out offset));
|
||||
}
|
||||
|
||||
endOffset = offset;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IDnsResourceRecord GetFromArray(Byte[] message, Int32 offset, out Int32 endOffset) {
|
||||
DnsResourceRecord record = DnsResourceRecord.FromArray(message, offset, out endOffset);
|
||||
Int32 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,215 +1,205 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using Formatters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// DnsClient Response inner class.
|
||||
/// </summary>
|
||||
internal partial class DnsClient
|
||||
{
|
||||
public class DnsClientResponse : IDnsResponse
|
||||
{
|
||||
private readonly DnsResponse _response;
|
||||
private readonly byte[] _message;
|
||||
|
||||
internal DnsClientResponse(DnsClientRequest request, DnsResponse response, byte[] message)
|
||||
{
|
||||
Request = request;
|
||||
|
||||
_message = message;
|
||||
_response = response;
|
||||
}
|
||||
|
||||
public DnsClientRequest Request { get; }
|
||||
|
||||
public int Id
|
||||
{
|
||||
get { return _response.Id; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public IList<IDnsResourceRecord> AnswerRecords => _response.AnswerRecords;
|
||||
|
||||
public IList<IDnsResourceRecord> AuthorityRecords =>
|
||||
new ReadOnlyCollection<IDnsResourceRecord>(_response.AuthorityRecords);
|
||||
|
||||
public IList<IDnsResourceRecord> AdditionalRecords =>
|
||||
new ReadOnlyCollection<IDnsResourceRecord>(_response.AdditionalRecords);
|
||||
|
||||
public bool IsRecursionAvailable
|
||||
{
|
||||
get { return _response.IsRecursionAvailable; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public bool IsAuthorativeServer
|
||||
{
|
||||
get { return _response.IsAuthorativeServer; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public bool IsTruncated
|
||||
{
|
||||
get { return _response.IsTruncated; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public DnsOperationCode OperationCode
|
||||
{
|
||||
get { return _response.OperationCode; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public DnsResponseCode ResponseCode
|
||||
{
|
||||
get { return _response.ResponseCode; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public IList<DnsQuestion> Questions => new ReadOnlyCollection<DnsQuestion>(_response.Questions);
|
||||
|
||||
public int Size => _message.Length;
|
||||
|
||||
public byte[] ToArray() => _message;
|
||||
|
||||
public override string ToString() => _response.ToString();
|
||||
}
|
||||
|
||||
public class DnsResponse : IDnsResponse
|
||||
{
|
||||
private DnsHeader _header;
|
||||
|
||||
public DnsResponse(
|
||||
DnsHeader header,
|
||||
IList<DnsQuestion> questions,
|
||||
IList<IDnsResourceRecord> answers,
|
||||
IList<IDnsResourceRecord> authority,
|
||||
IList<IDnsResourceRecord> additional)
|
||||
{
|
||||
_header = header;
|
||||
Questions = questions;
|
||||
AnswerRecords = answers;
|
||||
AuthorityRecords = authority;
|
||||
AdditionalRecords = additional;
|
||||
}
|
||||
|
||||
public IList<DnsQuestion> Questions { get; }
|
||||
|
||||
public IList<IDnsResourceRecord> AnswerRecords { get; }
|
||||
|
||||
public IList<IDnsResourceRecord> AuthorityRecords { get; }
|
||||
|
||||
public IList<IDnsResourceRecord> AdditionalRecords { get; }
|
||||
|
||||
public int Id
|
||||
{
|
||||
get => _header.Id;
|
||||
set => _header.Id = value;
|
||||
}
|
||||
|
||||
public bool IsRecursionAvailable
|
||||
{
|
||||
get => _header.RecursionAvailable;
|
||||
set => _header.RecursionAvailable = value;
|
||||
}
|
||||
|
||||
public bool IsAuthorativeServer
|
||||
{
|
||||
get => _header.AuthorativeServer;
|
||||
set => _header.AuthorativeServer = value;
|
||||
}
|
||||
|
||||
public bool IsTruncated
|
||||
{
|
||||
get => _header.Truncated;
|
||||
set => _header.Truncated = value;
|
||||
}
|
||||
|
||||
public DnsOperationCode OperationCode
|
||||
{
|
||||
get => _header.OperationCode;
|
||||
set => _header.OperationCode = value;
|
||||
}
|
||||
|
||||
public DnsResponseCode ResponseCode
|
||||
{
|
||||
get => _header.ResponseCode;
|
||||
set => _header.ResponseCode = value;
|
||||
}
|
||||
|
||||
public int Size
|
||||
=> _header.Size +
|
||||
Questions.Sum(q => q.Size) +
|
||||
AnswerRecords.Sum(a => a.Size) +
|
||||
AuthorityRecords.Sum(a => a.Size) +
|
||||
AdditionalRecords.Sum(a => a.Size);
|
||||
|
||||
public static DnsResponse FromArray(byte[] message)
|
||||
{
|
||||
var header = DnsHeader.FromArray(message);
|
||||
var offset = header.Size;
|
||||
|
||||
if (!header.Response || header.QuestionCount == 0)
|
||||
{
|
||||
throw new ArgumentException("Invalid response message");
|
||||
}
|
||||
|
||||
if (header.Truncated)
|
||||
{
|
||||
return new DnsResponse(header,
|
||||
DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount),
|
||||
new List<IDnsResourceRecord>(),
|
||||
new List<IDnsResourceRecord>(),
|
||||
new List<IDnsResourceRecord>());
|
||||
}
|
||||
|
||||
return new DnsResponse(header,
|
||||
DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount, out offset),
|
||||
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AnswerRecordCount, out offset),
|
||||
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AuthorityRecordCount, out offset),
|
||||
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AdditionalRecordCount, out offset));
|
||||
}
|
||||
|
||||
public byte[] ToArray()
|
||||
{
|
||||
UpdateHeader();
|
||||
var result = new MemoryStream(Size);
|
||||
|
||||
result
|
||||
.Append(_header.ToArray())
|
||||
.Append(Questions.Select(q => q.ToArray()))
|
||||
.Append(AnswerRecords.Select(a => a.ToArray()))
|
||||
.Append(AuthorityRecords.Select(a => a.ToArray()))
|
||||
.Append(AdditionalRecords.Select(a => a.ToArray()));
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
UpdateHeader();
|
||||
|
||||
return Json.SerializeOnly(
|
||||
this,
|
||||
true,
|
||||
nameof(Questions),
|
||||
nameof(AnswerRecords),
|
||||
nameof(AuthorityRecords),
|
||||
nameof(AdditionalRecords));
|
||||
}
|
||||
|
||||
private void UpdateHeader()
|
||||
{
|
||||
_header.QuestionCount = Questions.Count;
|
||||
_header.AnswerRecordCount = AnswerRecords.Count;
|
||||
_header.AuthorityRecordCount = AuthorityRecords.Count;
|
||||
_header.AdditionalRecordCount = AdditionalRecords.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
using Unosquare.Swan.Formatters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <summary>
|
||||
/// DnsClient Response inner class.
|
||||
/// </summary>
|
||||
internal partial class DnsClient {
|
||||
public class DnsClientResponse : IDnsResponse {
|
||||
private readonly DnsResponse _response;
|
||||
private readonly Byte[] _message;
|
||||
|
||||
internal DnsClientResponse(DnsClientRequest request, DnsResponse response, Byte[] message) {
|
||||
this.Request = request;
|
||||
|
||||
this._message = message;
|
||||
this._response = response;
|
||||
}
|
||||
|
||||
public DnsClientRequest Request {
|
||||
get;
|
||||
}
|
||||
|
||||
public Int32 Id {
|
||||
get => this._response.Id;
|
||||
set {
|
||||
}
|
||||
}
|
||||
|
||||
public IList<IDnsResourceRecord> AnswerRecords => this._response.AnswerRecords;
|
||||
|
||||
public IList<IDnsResourceRecord> AuthorityRecords =>
|
||||
new ReadOnlyCollection<IDnsResourceRecord>(this._response.AuthorityRecords);
|
||||
|
||||
public IList<IDnsResourceRecord> AdditionalRecords =>
|
||||
new ReadOnlyCollection<IDnsResourceRecord>(this._response.AdditionalRecords);
|
||||
|
||||
public Boolean IsRecursionAvailable {
|
||||
get => this._response.IsRecursionAvailable;
|
||||
set {
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean IsAuthorativeServer {
|
||||
get => this._response.IsAuthorativeServer;
|
||||
set {
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean IsTruncated {
|
||||
get => this._response.IsTruncated;
|
||||
set {
|
||||
}
|
||||
}
|
||||
|
||||
public DnsOperationCode OperationCode {
|
||||
get => this._response.OperationCode;
|
||||
set {
|
||||
}
|
||||
}
|
||||
|
||||
public DnsResponseCode ResponseCode {
|
||||
get => this._response.ResponseCode;
|
||||
set {
|
||||
}
|
||||
}
|
||||
|
||||
public IList<DnsQuestion> Questions => new ReadOnlyCollection<DnsQuestion>(this._response.Questions);
|
||||
|
||||
public Int32 Size => this._message.Length;
|
||||
|
||||
public Byte[] ToArray() => this._message;
|
||||
|
||||
public override String ToString() => this._response.ToString();
|
||||
}
|
||||
|
||||
public class DnsResponse : IDnsResponse {
|
||||
private DnsHeader _header;
|
||||
|
||||
public DnsResponse(
|
||||
DnsHeader header,
|
||||
IList<DnsQuestion> questions,
|
||||
IList<IDnsResourceRecord> answers,
|
||||
IList<IDnsResourceRecord> authority,
|
||||
IList<IDnsResourceRecord> additional) {
|
||||
this._header = header;
|
||||
this.Questions = questions;
|
||||
this.AnswerRecords = answers;
|
||||
this.AuthorityRecords = authority;
|
||||
this.AdditionalRecords = additional;
|
||||
}
|
||||
|
||||
public IList<DnsQuestion> Questions {
|
||||
get;
|
||||
}
|
||||
|
||||
public IList<IDnsResourceRecord> AnswerRecords {
|
||||
get;
|
||||
}
|
||||
|
||||
public IList<IDnsResourceRecord> AuthorityRecords {
|
||||
get;
|
||||
}
|
||||
|
||||
public IList<IDnsResourceRecord> AdditionalRecords {
|
||||
get;
|
||||
}
|
||||
|
||||
public Int32 Id {
|
||||
get => this._header.Id;
|
||||
set => this._header.Id = value;
|
||||
}
|
||||
|
||||
public Boolean IsRecursionAvailable {
|
||||
get => this._header.RecursionAvailable;
|
||||
set => this._header.RecursionAvailable = value;
|
||||
}
|
||||
|
||||
public Boolean IsAuthorativeServer {
|
||||
get => this._header.AuthorativeServer;
|
||||
set => this._header.AuthorativeServer = value;
|
||||
}
|
||||
|
||||
public Boolean IsTruncated {
|
||||
get => this._header.Truncated;
|
||||
set => this._header.Truncated = value;
|
||||
}
|
||||
|
||||
public DnsOperationCode OperationCode {
|
||||
get => this._header.OperationCode;
|
||||
set => this._header.OperationCode = value;
|
||||
}
|
||||
|
||||
public DnsResponseCode ResponseCode {
|
||||
get => this._header.ResponseCode;
|
||||
set => this._header.ResponseCode = value;
|
||||
}
|
||||
|
||||
public Int32 Size
|
||||
=> this._header.Size +
|
||||
this.Questions.Sum(q => q.Size) +
|
||||
this.AnswerRecords.Sum(a => a.Size) +
|
||||
this.AuthorityRecords.Sum(a => a.Size) +
|
||||
this.AdditionalRecords.Sum(a => a.Size);
|
||||
|
||||
public static DnsResponse FromArray(Byte[] message) {
|
||||
DnsHeader header = DnsHeader.FromArray(message);
|
||||
Int32 offset = header.Size;
|
||||
|
||||
if(!header.Response || header.QuestionCount == 0) {
|
||||
throw new ArgumentException("Invalid response message");
|
||||
}
|
||||
|
||||
return header.Truncated
|
||||
? new DnsResponse(header,
|
||||
DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount),
|
||||
new List<IDnsResourceRecord>(),
|
||||
new List<IDnsResourceRecord>(),
|
||||
new List<IDnsResourceRecord>())
|
||||
: new DnsResponse(header,
|
||||
DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount, out offset),
|
||||
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AnswerRecordCount, out offset),
|
||||
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AuthorityRecordCount, out offset),
|
||||
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AdditionalRecordCount, out _));
|
||||
}
|
||||
|
||||
public Byte[] ToArray() {
|
||||
this.UpdateHeader();
|
||||
MemoryStream result = new MemoryStream(this.Size);
|
||||
|
||||
_ = result
|
||||
.Append(this._header.ToArray())
|
||||
.Append(this.Questions.Select(q => q.ToArray()))
|
||||
.Append(this.AnswerRecords.Select(a => a.ToArray()))
|
||||
.Append(this.AuthorityRecords.Select(a => a.ToArray()))
|
||||
.Append(this.AdditionalRecords.Select(a => a.ToArray()));
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public override String ToString() {
|
||||
this.UpdateHeader();
|
||||
|
||||
return Json.SerializeOnly(
|
||||
this,
|
||||
true,
|
||||
nameof(this.Questions),
|
||||
nameof(this.AnswerRecords),
|
||||
nameof(this.AuthorityRecords),
|
||||
nameof(this.AdditionalRecords));
|
||||
}
|
||||
|
||||
private void UpdateHeader() {
|
||||
this._header.QuestionCount = this.Questions.Count;
|
||||
this._header.AnswerRecordCount = this.AnswerRecords.Count;
|
||||
this._header.AuthorityRecordCount = this.AuthorityRecords.Count;
|
||||
this._header.AdditionalRecordCount = this.AdditionalRecords.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,88 +1,77 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// DnsClient public methods.
|
||||
/// </summary>
|
||||
internal partial class DnsClient
|
||||
{
|
||||
private readonly IPEndPoint _dns;
|
||||
private readonly IDnsRequestResolver _resolver;
|
||||
|
||||
public DnsClient(IPEndPoint dns, IDnsRequestResolver resolver = null)
|
||||
{
|
||||
_dns = dns;
|
||||
_resolver = resolver ?? new DnsUdpRequestResolver(new DnsTcpRequestResolver());
|
||||
}
|
||||
|
||||
public DnsClient(IPAddress ip, int port = Network.DnsDefaultPort, IDnsRequestResolver resolver = null)
|
||||
: this(new IPEndPoint(ip, port), resolver)
|
||||
{
|
||||
}
|
||||
|
||||
public DnsClientRequest Create(IDnsRequest request = null)
|
||||
{
|
||||
return new DnsClientRequest(_dns, request, _resolver);
|
||||
}
|
||||
|
||||
public IList<IPAddress> Lookup(string domain, DnsRecordType type = DnsRecordType.A)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(domain))
|
||||
throw new ArgumentNullException(nameof(domain));
|
||||
|
||||
if (type != DnsRecordType.A && type != DnsRecordType.AAAA)
|
||||
{
|
||||
throw new ArgumentException("Invalid record type " + type);
|
||||
}
|
||||
|
||||
var response = Resolve(domain, type);
|
||||
var ips = response.AnswerRecords
|
||||
.Where(r => r.Type == type)
|
||||
.Cast<DnsIPAddressResourceRecord>()
|
||||
.Select(r => r.IPAddress)
|
||||
.ToList();
|
||||
|
||||
if (ips.Count == 0)
|
||||
{
|
||||
throw new DnsQueryException(response, "No matching records");
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
|
||||
public string Reverse(IPAddress ip)
|
||||
{
|
||||
if (ip == null)
|
||||
throw new ArgumentNullException(nameof(ip));
|
||||
|
||||
var response = Resolve(DnsDomain.PointerName(ip), DnsRecordType.PTR);
|
||||
var ptr = response.AnswerRecords.FirstOrDefault(r => r.Type == DnsRecordType.PTR);
|
||||
|
||||
if (ptr == null)
|
||||
{
|
||||
throw new DnsQueryException(response, "No matching records");
|
||||
}
|
||||
|
||||
return ((DnsPointerResourceRecord)ptr).PointerDomainName.ToString();
|
||||
}
|
||||
|
||||
public DnsClientResponse Resolve(string domain, DnsRecordType type) => Resolve(new DnsDomain(domain), type);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Unosquare.Swan.Exceptions;
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <summary>
|
||||
/// DnsClient public methods.
|
||||
/// </summary>
|
||||
internal partial class DnsClient {
|
||||
private readonly IPEndPoint _dns;
|
||||
private readonly IDnsRequestResolver _resolver;
|
||||
|
||||
public DnsClient(IPEndPoint dns, IDnsRequestResolver resolver = null) {
|
||||
this._dns = dns;
|
||||
this._resolver = resolver ?? new DnsUdpRequestResolver(new DnsTcpRequestResolver());
|
||||
}
|
||||
|
||||
public DnsClient(IPAddress ip, Int32 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)) {
|
||||
throw new ArgumentNullException(nameof(domain));
|
||||
}
|
||||
|
||||
if(type != DnsRecordType.A && type != DnsRecordType.AAAA) {
|
||||
throw new ArgumentException("Invalid record type " + type);
|
||||
}
|
||||
|
||||
DnsClientResponse response = this.Resolve(domain, type);
|
||||
List<IPAddress> ips = response.AnswerRecords
|
||||
.Where(r => r.Type == type)
|
||||
.Cast<DnsIPAddressResourceRecord>()
|
||||
.Select(r => r.IPAddress)
|
||||
.ToList();
|
||||
|
||||
if(ips.Count == 0) {
|
||||
throw new DnsQueryException(response, "No matching records");
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
|
||||
public String Reverse(IPAddress ip) {
|
||||
if(ip == null) {
|
||||
throw new ArgumentNullException(nameof(ip));
|
||||
}
|
||||
|
||||
DnsClientResponse response = this.Resolve(DnsDomain.PointerName(ip), DnsRecordType.PTR);
|
||||
IDnsResourceRecord ptr = response.AnswerRecords.FirstOrDefault(r => r.Type == DnsRecordType.PTR);
|
||||
|
||||
if(ptr == null) {
|
||||
throw new DnsQueryException(response, "No matching records");
|
||||
}
|
||||
|
||||
return ((DnsPointerResourceRecord)ptr).PointerDomainName.ToString();
|
||||
}
|
||||
|
||||
public DnsClientResponse Resolve(String domain, DnsRecordType type) => this.Resolve(new DnsDomain(domain), type);
|
||||
|
||||
public DnsClientResponse Resolve(DnsDomain domain, DnsRecordType type) {
|
||||
DnsClientRequest request = this.Create();
|
||||
DnsQuestion question = new DnsQuestion(domain, type);
|
||||
|
||||
request.Questions.Add(question);
|
||||
request.OperationCode = DnsOperationCode.Query;
|
||||
request.RecursionDesired = true;
|
||||
|
||||
return request.Resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,123 +1,132 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System;
|
||||
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>
|
||||
/// Represents a response from a DNS server.
|
||||
/// Initializes a new instance of the <see cref="DnsQueryResult"/> class.
|
||||
/// </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>
|
||||
/// Initializes a new instance of the <see cref="DnsQueryResult"/> class.
|
||||
/// </summary>
|
||||
/// <param name="response">The response.</param>
|
||||
internal DnsQueryResult(DnsClient.DnsClientResponse response)
|
||||
: this()
|
||||
{
|
||||
Id = response.Id;
|
||||
IsAuthoritativeServer = response.IsAuthorativeServer;
|
||||
IsRecursionAvailable = response.IsRecursionAvailable;
|
||||
IsTruncated = response.IsTruncated;
|
||||
OperationCode = response.OperationCode;
|
||||
ResponseCode = response.ResponseCode;
|
||||
|
||||
if (response.AnswerRecords != null)
|
||||
{
|
||||
foreach (var record in response.AnswerRecords)
|
||||
AnswerRecords.Add(new DnsRecord(record));
|
||||
}
|
||||
|
||||
if (response.AuthorityRecords != null)
|
||||
{
|
||||
foreach (var record in response.AuthorityRecords)
|
||||
AuthorityRecords.Add(new DnsRecord(record));
|
||||
}
|
||||
|
||||
if (response.AdditionalRecords != null)
|
||||
{
|
||||
foreach (var record in response.AdditionalRecords)
|
||||
AdditionalRecords.Add(new DnsRecord(record));
|
||||
}
|
||||
}
|
||||
|
||||
private DnsQueryResult()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The identifier.
|
||||
/// </value>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is authoritative server.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is authoritative server; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsAuthoritativeServer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is truncated.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is truncated; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsTruncated { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is recursion available.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is recursion available; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsRecursionAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the operation code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The operation code.
|
||||
/// </value>
|
||||
public DnsOperationCode OperationCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The response code.
|
||||
/// </value>
|
||||
public DnsResponseCode ResponseCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the answer records.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The answer records.
|
||||
/// </value>
|
||||
public IList<DnsRecord> AnswerRecords => m_AnswerRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the additional records.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The additional records.
|
||||
/// </value>
|
||||
public IList<DnsRecord> AdditionalRecords => m_AdditionalRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authority records.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authority records.
|
||||
/// </value>
|
||||
public IList<DnsRecord> AuthorityRecords => m_AuthorityRecords;
|
||||
}
|
||||
/// <param name="response">The response.</param>
|
||||
internal DnsQueryResult(DnsClient.DnsClientResponse response)
|
||||
: this() {
|
||||
this.Id = response.Id;
|
||||
this.IsAuthoritativeServer = response.IsAuthorativeServer;
|
||||
this.IsRecursionAvailable = response.IsRecursionAvailable;
|
||||
this.IsTruncated = response.IsTruncated;
|
||||
this.OperationCode = response.OperationCode;
|
||||
this.ResponseCode = response.ResponseCode;
|
||||
|
||||
if(response.AnswerRecords != null) {
|
||||
foreach(DnsClient.IDnsResourceRecord record in response.AnswerRecords) {
|
||||
this.AnswerRecords.Add(new DnsRecord(record));
|
||||
}
|
||||
}
|
||||
|
||||
if(response.AuthorityRecords != null) {
|
||||
foreach(DnsClient.IDnsResourceRecord record in response.AuthorityRecords) {
|
||||
this.AuthorityRecords.Add(new DnsRecord(record));
|
||||
}
|
||||
}
|
||||
|
||||
if(response.AdditionalRecords != null) {
|
||||
foreach(DnsClient.IDnsResourceRecord record in response.AdditionalRecords) {
|
||||
this.AdditionalRecords.Add(new DnsRecord(record));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DnsQueryResult() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The identifier.
|
||||
/// </value>
|
||||
public Int32 Id {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is authoritative server.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is authoritative server; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsAuthoritativeServer {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is truncated.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is truncated; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsTruncated {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is recursion available.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is recursion available; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean IsRecursionAvailable {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the operation code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The operation code.
|
||||
/// </value>
|
||||
public DnsOperationCode OperationCode {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The response code.
|
||||
/// </value>
|
||||
public DnsResponseCode ResponseCode {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the answer records.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The answer records.
|
||||
/// </value>
|
||||
public IList<DnsRecord> AnswerRecords => this.m_AnswerRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the additional records.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The additional records.
|
||||
/// </value>
|
||||
public IList<DnsRecord> AdditionalRecords => this.m_AdditionalRecords;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authority records.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authority records.
|
||||
/// </value>
|
||||
public IList<DnsRecord> AuthorityRecords => this.m_AuthorityRecords;
|
||||
}
|
||||
}
|
||||
|
@ -1,208 +1,240 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <summary>
|
||||
/// Represents a DNS record entry.
|
||||
/// </summary>
|
||||
public class DnsRecord {
|
||||
/// <summary>
|
||||
/// Represents a DNS record entry.
|
||||
/// Initializes a new instance of the <see cref="DnsRecord"/> class.
|
||||
/// </summary>
|
||||
public class DnsRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DnsRecord"/> class.
|
||||
/// </summary>
|
||||
/// <param name="record">The record.</param>
|
||||
internal DnsRecord(DnsClient.IDnsResourceRecord record)
|
||||
: this()
|
||||
{
|
||||
Name = record.Name.ToString();
|
||||
Type = record.Type;
|
||||
Class = record.Class;
|
||||
TimeToLive = record.TimeToLive;
|
||||
Data = record.Data;
|
||||
|
||||
// PTR
|
||||
PointerDomainName = (record as DnsClient.DnsPointerResourceRecord)?.PointerDomainName?.ToString();
|
||||
|
||||
// A
|
||||
IPAddress = (record as DnsClient.DnsIPAddressResourceRecord)?.IPAddress;
|
||||
|
||||
// NS
|
||||
NameServerDomainName = (record as DnsClient.DnsNameServerResourceRecord)?.NSDomainName?.ToString();
|
||||
|
||||
// CNAME
|
||||
CanonicalDomainName = (record as DnsClient.DnsCanonicalNameResourceRecord)?.CanonicalDomainName.ToString();
|
||||
|
||||
// MX
|
||||
MailExchangerDomainName = (record as DnsClient.DnsMailExchangeResourceRecord)?.ExchangeDomainName.ToString();
|
||||
MailExchangerPreference = (record as DnsClient.DnsMailExchangeResourceRecord)?.Preference;
|
||||
|
||||
// SOA
|
||||
SoaMasterDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MasterDomainName.ToString();
|
||||
SoaResponsibleDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ResponsibleDomainName.ToString();
|
||||
SoaSerialNumber = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.SerialNumber;
|
||||
SoaRefreshInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RefreshInterval;
|
||||
SoaRetryInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RetryInterval;
|
||||
SoaExpireInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ExpireInterval;
|
||||
SoaMinimumTimeToLive = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MinimumTimeToLive;
|
||||
}
|
||||
|
||||
private DnsRecord()
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public DnsRecordType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the class.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The class.
|
||||
/// </value>
|
||||
public DnsRecordClass Class { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time to live.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The time to live.
|
||||
/// </value>
|
||||
public TimeSpan TimeToLive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw data of the record.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The data.
|
||||
/// </value>
|
||||
public byte[] Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data text bytes in ASCII encoding.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The data text.
|
||||
/// </value>
|
||||
public string DataText => Data == null ? string.Empty : Encoding.ASCII.GetString(Data);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the pointer domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the pointer domain.
|
||||
/// </value>
|
||||
public string PointerDomainName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ip address.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ip address.
|
||||
/// </value>
|
||||
public IPAddress IPAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the name server domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the name server domain.
|
||||
/// </value>
|
||||
public string NameServerDomainName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the canonical domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the canonical domain.
|
||||
/// </value>
|
||||
public string CanonicalDomainName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mail exchanger preference.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The mail exchanger preference.
|
||||
/// </value>
|
||||
public int? MailExchangerPreference { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the mail exchanger domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the mail exchanger domain.
|
||||
/// </value>
|
||||
public string MailExchangerDomainName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the soa master domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the soa master domain.
|
||||
/// </value>
|
||||
public string SoaMasterDomainName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the soa responsible domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the soa responsible domain.
|
||||
/// </value>
|
||||
public string SoaResponsibleDomainName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the soa serial number.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The soa serial number.
|
||||
/// </value>
|
||||
public long? SoaSerialNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the soa refresh interval.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The soa refresh interval.
|
||||
/// </value>
|
||||
public TimeSpan? SoaRefreshInterval { 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; }
|
||||
}
|
||||
/// <param name="record">The record.</param>
|
||||
internal DnsRecord(DnsClient.IDnsResourceRecord record)
|
||||
: this() {
|
||||
this.Name = record.Name.ToString();
|
||||
this.Type = record.Type;
|
||||
this.Class = record.Class;
|
||||
this.TimeToLive = record.TimeToLive;
|
||||
this.Data = record.Data;
|
||||
|
||||
// PTR
|
||||
this.PointerDomainName = (record as DnsClient.DnsPointerResourceRecord)?.PointerDomainName?.ToString();
|
||||
|
||||
// A
|
||||
this.IPAddress = (record as DnsClient.DnsIPAddressResourceRecord)?.IPAddress;
|
||||
|
||||
// NS
|
||||
this.NameServerDomainName = (record as DnsClient.DnsNameServerResourceRecord)?.NSDomainName?.ToString();
|
||||
|
||||
// CNAME
|
||||
this.CanonicalDomainName = (record as DnsClient.DnsCanonicalNameResourceRecord)?.CanonicalDomainName.ToString();
|
||||
|
||||
// MX
|
||||
this.MailExchangerDomainName = (record as DnsClient.DnsMailExchangeResourceRecord)?.ExchangeDomainName.ToString();
|
||||
this.MailExchangerPreference = (record as DnsClient.DnsMailExchangeResourceRecord)?.Preference;
|
||||
|
||||
// SOA
|
||||
this.SoaMasterDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MasterDomainName.ToString();
|
||||
this.SoaResponsibleDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ResponsibleDomainName.ToString();
|
||||
this.SoaSerialNumber = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.SerialNumber;
|
||||
this.SoaRefreshInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RefreshInterval;
|
||||
this.SoaRetryInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RetryInterval;
|
||||
this.SoaExpireInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ExpireInterval;
|
||||
this.SoaMinimumTimeToLive = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MinimumTimeToLive;
|
||||
}
|
||||
|
||||
private DnsRecord() {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public String Name {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public DnsRecordType Type {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the class.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The class.
|
||||
/// </value>
|
||||
public DnsRecordClass Class {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time to live.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The time to live.
|
||||
/// </value>
|
||||
public TimeSpan TimeToLive {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw data of the record.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The data.
|
||||
/// </value>
|
||||
public Byte[] Data {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data text bytes in ASCII encoding.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The data text.
|
||||
/// </value>
|
||||
public String DataText => this.Data == null ? String.Empty : Encoding.ASCII.GetString(this.Data);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the pointer domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the pointer domain.
|
||||
/// </value>
|
||||
public String PointerDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ip address.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ip address.
|
||||
/// </value>
|
||||
public IPAddress IPAddress {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the name server domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the name server domain.
|
||||
/// </value>
|
||||
public String NameServerDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the canonical domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the canonical domain.
|
||||
/// </value>
|
||||
public String CanonicalDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mail exchanger preference.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The mail exchanger preference.
|
||||
/// </value>
|
||||
public Int32? MailExchangerPreference {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the mail exchanger domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the mail exchanger domain.
|
||||
/// </value>
|
||||
public String MailExchangerDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the soa master domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the soa master domain.
|
||||
/// </value>
|
||||
public String SoaMasterDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the soa responsible domain.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the soa responsible domain.
|
||||
/// </value>
|
||||
public String SoaResponsibleDomainName {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the soa serial number.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The soa serial number.
|
||||
/// </value>
|
||||
public Int64? SoaSerialNumber {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the soa refresh interval.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The soa refresh interval.
|
||||
/// </value>
|
||||
public TimeSpan? SoaRefreshInterval {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,177 +1,172 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
#region DNS
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
#region DNS
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different DNS record types.
|
||||
/// </summary>
|
||||
public enum DnsRecordType {
|
||||
/// <summary>
|
||||
/// Enumerates the different DNS record types.
|
||||
/// </summary>
|
||||
public enum DnsRecordType
|
||||
{
|
||||
/// <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,
|
||||
}
|
||||
|
||||
/// A records
|
||||
/// </summary>
|
||||
A = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different DNS record classes.
|
||||
/// </summary>
|
||||
public enum DnsRecordClass
|
||||
{
|
||||
/// <summary>
|
||||
/// IN records
|
||||
/// </summary>
|
||||
IN = 1,
|
||||
|
||||
/// <summary>
|
||||
/// ANY records
|
||||
/// </summary>
|
||||
ANY = 255,
|
||||
}
|
||||
|
||||
/// Nameserver records
|
||||
/// </summary>
|
||||
NS = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different DNS operation codes.
|
||||
/// </summary>
|
||||
public enum DnsOperationCode
|
||||
{
|
||||
/// <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,
|
||||
}
|
||||
|
||||
/// CNAME records
|
||||
/// </summary>
|
||||
CNAME = 5,
|
||||
|
||||
/// <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
|
||||
|
||||
/// 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>
|
||||
/// Enumerates the different DNS record classes.
|
||||
/// </summary>
|
||||
public enum DnsRecordClass {
|
||||
/// <summary>
|
||||
/// IN records
|
||||
/// </summary>
|
||||
IN = 1,
|
||||
|
||||
/// <summary>
|
||||
/// ANY records
|
||||
/// </summary>
|
||||
ANY = 255,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different DNS operation codes.
|
||||
/// </summary>
|
||||
public enum DnsOperationCode {
|
||||
/// <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>
|
||||
/// 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
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
namespace Unosquare.Swan.Networking {
|
||||
#if NETSTANDARD1_3
|
||||
|
||||
/// <summary>
|
||||
@ -134,167 +133,164 @@ namespace Unosquare.Swan.Networking
|
||||
GeneralFailure = -1,
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates all of the well-known SMTP command names.
|
||||
/// </summary>
|
||||
public enum SmtpCommandNames {
|
||||
/// <summary>
|
||||
/// Enumerates all of the well-known SMTP command names.
|
||||
/// An unknown command
|
||||
/// </summary>
|
||||
public enum SmtpCommandNames
|
||||
{
|
||||
/// <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,
|
||||
}
|
||||
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the reply code severities.
|
||||
/// The helo command
|
||||
/// </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,
|
||||
}
|
||||
|
||||
HELO,
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the reply code categories.
|
||||
/// The ehlo command
|
||||
/// </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,
|
||||
}
|
||||
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>
|
||||
/// 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,
|
||||
}
|
||||
}
|
@ -1,400 +1,378 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System;
|
||||
using Exceptions;
|
||||
using Models;
|
||||
using Formatters;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
using Unosquare.Swan.Exceptions;
|
||||
using Unosquare.Swan.Models;
|
||||
using Unosquare.Swan.Formatters;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
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>
|
||||
/// Represents a HttpClient with extended methods to use with JSON payloads
|
||||
/// and bearer tokens authentication.
|
||||
/// Post a object as JSON with optional authorization token.
|
||||
/// </summary>
|
||||
public static class JsonClient
|
||||
{
|
||||
private const string JsonMimeType = "application/json";
|
||||
|
||||
/// <summary>
|
||||
/// Post a object as JSON with optional authorization token.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of response object.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested type.</returns>
|
||||
public static async Task<T> Post<T>(
|
||||
string url,
|
||||
object payload,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts a object as JSON with optional authorization token and retrieve an object
|
||||
/// or an error.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of response object.</typeparam>
|
||||
/// <typeparam name="TE">The type of the error.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="httpStatusError">The HTTP status error.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested type or an error object.</returns>
|
||||
public static async Task<OkOrError<T, TE>> PostOrError<T, TE>(
|
||||
string url,
|
||||
object payload,
|
||||
int httpStatusError = 500,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
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)
|
||||
/// <typeparam name="T">The type of response object.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested type.</returns>
|
||||
public static async Task<T> Post<T>(
|
||||
String url,
|
||||
Object payload,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
String jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts a object as JSON with optional authorization token and retrieve an object
|
||||
/// or an error.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of response object.</typeparam>
|
||||
/// <typeparam name="TE">The type of the error.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="httpStatusError">The HTTP status error.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested type or an error object.</returns>
|
||||
public static async Task<OkOrError<T, TE>> PostOrError<T, TE>(
|
||||
String url,
|
||||
Object payload,
|
||||
Int32 httpStatusError = 500,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
using(HttpClient httpClient = GetHttpClientWithAuthorizationHeader(authorization)) {
|
||||
StringContent payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
|
||||
|
||||
HttpResponseMessage response = await httpClient.PostAsync(url, payloadJson, ct).ConfigureAwait(false);
|
||||
|
||||
String jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
return response.StatusCode == System.Net.HttpStatusCode.OK
|
||||
? OkOrError<T, TE>.FromOk(!String.IsNullOrEmpty(jsonString)
|
||||
? Json.Deserialize<T>(jsonString)
|
||||
: default);
|
||||
}
|
||||
|
||||
if ((int) response.StatusCode == httpStatusError)
|
||||
{
|
||||
return OkOrError<T, TE>.FromError(!string.IsNullOrEmpty(jsonString)
|
||||
: default)
|
||||
: (Int32)response.StatusCode == httpStatusError
|
||||
? OkOrError<T, TE>.FromError(!String.IsNullOrEmpty(jsonString)
|
||||
? Json.Deserialize<TE>(jsonString)
|
||||
: default);
|
||||
}
|
||||
|
||||
return new OkOrError<T, TE>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the specified URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result as a collection of key/value pairs.</returns>
|
||||
public static async Task<IDictionary<string, object>> Post(
|
||||
string url,
|
||||
object payload,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return string.IsNullOrWhiteSpace(jsonString)
|
||||
? default
|
||||
: Json.Deserialize(jsonString) as IDictionary<string, object>;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the specified URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested string.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">url.</exception>
|
||||
/// <exception cref="JsonRequestException">Error POST JSON.</exception>
|
||||
public static Task<string> PostString(
|
||||
string url,
|
||||
object payload,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default) => SendAsync(HttpMethod.Post, url, payload, authorization, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Puts the specified URL.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of response object.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested type.</returns>
|
||||
public static async Task<T> Put<T>(
|
||||
string url,
|
||||
object payload,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var jsonString = await PutString(url, payload, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the specified URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested collection of key/value pairs.</returns>
|
||||
public static async Task<IDictionary<string, object>> Put(
|
||||
string url,
|
||||
object payload,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var response = await Put<object>(url, payload, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return response as IDictionary<string, object>;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts as string.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested string.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">url.</exception>
|
||||
/// <exception cref="JsonRequestException">Error PUT JSON.</exception>
|
||||
public static Task<string> PutString(
|
||||
string url,
|
||||
object payload,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default) => SendAsync(HttpMethod.Put, url, payload, authorization, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Gets as string.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested string.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">url.</exception>
|
||||
/// <exception cref="JsonRequestException">Error GET JSON.</exception>
|
||||
public static async Task<string> GetString(
|
||||
string url,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return await response.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified URL and return the JSON data as object
|
||||
/// with optional authorization token.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The response type.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested type.</returns>
|
||||
public static async Task<T> Get<T>(
|
||||
string url,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var jsonString = await GetString(url, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the binary.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested byte array.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">url.</exception>
|
||||
/// <exception cref="JsonRequestException">Error GET Binary.</exception>
|
||||
public static async Task<byte[]> GetBinary(
|
||||
string url,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return await response.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate against a web server using Bearer Token.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a Dictionary with authentication data.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// url
|
||||
/// or
|
||||
/// username.
|
||||
/// </exception>
|
||||
/// <exception cref="SecurityException">Error Authenticating.</exception>
|
||||
public static async Task<IDictionary<string, object>> Authenticate(
|
||||
string url,
|
||||
string username,
|
||||
string password,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
throw new ArgumentNullException(nameof(username));
|
||||
|
||||
using (var httpClient = new HttpClient())
|
||||
{
|
||||
// ignore empty password for now
|
||||
var requestContent = new StringContent(
|
||||
: default)
|
||||
: new OkOrError<T, TE>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the specified URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result as a collection of key/value pairs.</returns>
|
||||
public static async Task<IDictionary<String, Object>> Post(
|
||||
String url,
|
||||
Object payload,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
String jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return String.IsNullOrWhiteSpace(jsonString)
|
||||
? default
|
||||
: Json.Deserialize(jsonString) as IDictionary<String, Object>;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the specified URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested string.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">url.</exception>
|
||||
/// <exception cref="JsonRequestException">Error POST JSON.</exception>
|
||||
public static Task<String> PostString(
|
||||
String url,
|
||||
Object payload,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) => SendAsync(HttpMethod.Post, url, payload, authorization, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Puts the specified URL.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of response object.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested type.</returns>
|
||||
public static async Task<T> Put<T>(
|
||||
String url,
|
||||
Object payload,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
String jsonString = await PutString(url, payload, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the specified URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested collection of key/value pairs.</returns>
|
||||
public static async Task<IDictionary<String, Object>> Put(
|
||||
String url,
|
||||
Object payload,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
Object response = await Put<Object>(url, payload, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return response as IDictionary<String, Object>;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts as string.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested string.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">url.</exception>
|
||||
/// <exception cref="JsonRequestException">Error PUT JSON.</exception>
|
||||
public static Task<String> PutString(
|
||||
String url,
|
||||
Object payload,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) => SendAsync(HttpMethod.Put, url, payload, authorization, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Gets as string.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested string.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">url.</exception>
|
||||
/// <exception cref="JsonRequestException">Error GET JSON.</exception>
|
||||
public static async Task<String> GetString(
|
||||
String url,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
HttpContent response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return await response.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified URL and return the JSON data as object
|
||||
/// with optional authorization token.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The response type.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested type.</returns>
|
||||
public static async Task<T> Get<T>(
|
||||
String url,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
String jsonString = await GetString(url, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the binary.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested byte array.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">url.</exception>
|
||||
/// <exception cref="JsonRequestException">Error GET Binary.</exception>
|
||||
public static async Task<Byte[]> GetBinary(
|
||||
String url,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
HttpContent response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
|
||||
|
||||
return await response.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate against a web server using Bearer Token.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a Dictionary with authentication data.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// url
|
||||
/// or
|
||||
/// username.
|
||||
/// </exception>
|
||||
/// <exception cref="SecurityException">Error Authenticating.</exception>
|
||||
public static async Task<IDictionary<String, Object>> Authenticate(
|
||||
String url,
|
||||
String username,
|
||||
String password,
|
||||
CancellationToken ct = default) {
|
||||
if(String.IsNullOrWhiteSpace(url)) {
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
}
|
||||
|
||||
if(String.IsNullOrWhiteSpace(username)) {
|
||||
throw new ArgumentNullException(nameof(username));
|
||||
}
|
||||
|
||||
using(HttpClient httpClient = new HttpClient()) {
|
||||
// ignore empty password for now
|
||||
StringContent requestContent = new StringContent(
|
||||
$"grant_type=password&username={username}&password={password}",
|
||||
Encoding.UTF8,
|
||||
"application/x-www-form-urlencoded");
|
||||
var response = await httpClient.PostAsync(url, requestContent, ct).ConfigureAwait(false);
|
||||
|
||||
if (response.IsSuccessStatusCode == false)
|
||||
throw new SecurityException($"Error Authenticating. Status code: {response.StatusCode}.");
|
||||
|
||||
var jsonPayload = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
return Json.Deserialize(jsonPayload) as IDictionary<string, object>;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the file.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="fileName">Name of the file.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested string.
|
||||
/// </returns>
|
||||
public static Task<string> PostFileString(
|
||||
string url,
|
||||
byte[] buffer,
|
||||
string fileName,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return PostString(url, new {Filename = fileName, Data = buffer}, authorization, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the file.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The response type.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="fileName">Name of the file.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested string.</returns>
|
||||
public static Task<T> PostFile<T>(
|
||||
string url,
|
||||
byte[] buffer,
|
||||
string fileName,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Post<T>(url, new {Filename = fileName, Data = buffer}, authorization, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the asynchronous request.
|
||||
/// </summary>
|
||||
/// <param name="method">The method.</param>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested string.</returns>
|
||||
public static async Task<string> SendAsync(HttpMethod method,
|
||||
string url,
|
||||
object payload,
|
||||
string authorization = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
|
||||
using (var httpClient = GetHttpClientWithAuthorizationHeader(authorization))
|
||||
{
|
||||
var payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
|
||||
|
||||
var response = await httpClient
|
||||
.SendAsync(new HttpRequestMessage(method, url) {Content = payloadJson}, ct).ConfigureAwait(false);
|
||||
|
||||
if (response.IsSuccessStatusCode == false)
|
||||
{
|
||||
throw new JsonRequestException(
|
||||
$"Error {method} JSON",
|
||||
(int) response.StatusCode,
|
||||
await response.Content.ReadAsStringAsync().ConfigureAwait(false));
|
||||
}
|
||||
|
||||
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpClient GetHttpClientWithAuthorizationHeader(string authorization)
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authorization) == false)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Authorization =
|
||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authorization);
|
||||
}
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private static async Task<HttpContent> GetHttpContent(
|
||||
string url,
|
||||
string authorization,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
|
||||
using (var httpClient = GetHttpClientWithAuthorizationHeader(authorization))
|
||||
{
|
||||
var response = await httpClient.GetAsync(url, ct).ConfigureAwait(false);
|
||||
|
||||
if (response.IsSuccessStatusCode == false)
|
||||
throw new JsonRequestException("Error GET", (int) response.StatusCode);
|
||||
|
||||
return response.Content;
|
||||
}
|
||||
}
|
||||
}
|
||||
"application/x-www-form-urlencoded");
|
||||
HttpResponseMessage response = await httpClient.PostAsync(url, requestContent, ct).ConfigureAwait(false);
|
||||
|
||||
if(response.IsSuccessStatusCode == false) {
|
||||
throw new SecurityException($"Error Authenticating. Status code: {response.StatusCode}.");
|
||||
}
|
||||
|
||||
String jsonPayload = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
return Json.Deserialize(jsonPayload) as IDictionary<String, Object>;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the file.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="fileName">Name of the file.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task with a result of the requested string.
|
||||
/// </returns>
|
||||
public static Task<String> PostFileString(
|
||||
String url,
|
||||
Byte[] buffer,
|
||||
String fileName,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) => PostString(url, new {
|
||||
Filename = fileName, Data = buffer
|
||||
}, authorization, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Posts the file.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The response type.</typeparam>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="fileName">Name of the file.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested string.</returns>
|
||||
public static Task<T> PostFile<T>(
|
||||
String url,
|
||||
Byte[] buffer,
|
||||
String fileName,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) => Post<T>(url, new {
|
||||
Filename = fileName, Data = buffer
|
||||
}, authorization, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the asynchronous request.
|
||||
/// </summary>
|
||||
/// <param name="method">The method.</param>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="payload">The payload.</param>
|
||||
/// <param name="authorization">The authorization.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>A task with a result of the requested string.</returns>
|
||||
public static async Task<String> SendAsync(HttpMethod method,
|
||||
String url,
|
||||
Object payload,
|
||||
String authorization = null,
|
||||
CancellationToken ct = default) {
|
||||
if(String.IsNullOrWhiteSpace(url)) {
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
}
|
||||
|
||||
using(HttpClient httpClient = GetHttpClientWithAuthorizationHeader(authorization)) {
|
||||
StringContent payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
|
||||
|
||||
HttpResponseMessage response = await httpClient
|
||||
.SendAsync(new HttpRequestMessage(method, url) { Content = payloadJson }, ct).ConfigureAwait(false);
|
||||
|
||||
if(response.IsSuccessStatusCode == false) {
|
||||
throw new JsonRequestException(
|
||||
$"Error {method} JSON",
|
||||
(Int32)response.StatusCode,
|
||||
await response.Content.ReadAsStringAsync().ConfigureAwait(false));
|
||||
}
|
||||
|
||||
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpClient GetHttpClientWithAuthorizationHeader(String authorization) {
|
||||
HttpClient httpClient = new HttpClient();
|
||||
|
||||
if(String.IsNullOrWhiteSpace(authorization) == false) {
|
||||
httpClient.DefaultRequestHeaders.Authorization =
|
||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authorization);
|
||||
}
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private static async Task<HttpContent> GetHttpContent(
|
||||
String url,
|
||||
String authorization,
|
||||
CancellationToken ct) {
|
||||
if(String.IsNullOrWhiteSpace(url)) {
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
}
|
||||
|
||||
using(HttpClient httpClient = GetHttpClientWithAuthorizationHeader(authorization)) {
|
||||
HttpResponseMessage response = await httpClient.GetAsync(url, ct).ConfigureAwait(false);
|
||||
|
||||
if(response.IsSuccessStatusCode == false) {
|
||||
throw new JsonRequestException("Error GET", (Int32)response.StatusCode);
|
||||
}
|
||||
|
||||
return response.Content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,166 +1,165 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
using System.IO;
|
||||
using System;
|
||||
|
||||
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>
|
||||
/// 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.
|
||||
/// Decode an LBER encoded value into an Asn1Object from an InputStream.
|
||||
/// This method also returns the total length of this encoded
|
||||
/// Asn1Object (length of type + length of length + length of content)
|
||||
/// in the parameter len. This information is helpful when decoding
|
||||
/// structured types.
|
||||
/// </summary>
|
||||
internal static class LberDecoder
|
||||
{
|
||||
/// <summary>
|
||||
/// Decode an LBER encoded value into an Asn1Object from an InputStream.
|
||||
/// This method also returns the total length of this encoded
|
||||
/// Asn1Object (length of type + length of length + length of content)
|
||||
/// in the parameter len. This information is helpful when decoding
|
||||
/// structured types.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
/// <returns>
|
||||
/// Decoded Asn1Obect.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">Unknown tag.</exception>
|
||||
public static Asn1Object Decode(Stream stream, int[] len)
|
||||
{
|
||||
var asn1Id = new Asn1Identifier(stream);
|
||||
var asn1Len = new Asn1Length(stream);
|
||||
|
||||
var length = asn1Len.Length;
|
||||
len[0] = asn1Id.EncodedLength + asn1Len.EncodedLength + length;
|
||||
|
||||
if (asn1Id.Universal == false)
|
||||
return new Asn1Tagged(stream, length, (Asn1Identifier) asn1Id.Clone());
|
||||
|
||||
switch (asn1Id.Tag)
|
||||
{
|
||||
case Asn1Sequence.Tag:
|
||||
return new Asn1Sequence(stream, length);
|
||||
|
||||
case Asn1Set.Tag:
|
||||
return new Asn1Set(stream, length);
|
||||
|
||||
case Asn1Boolean.Tag:
|
||||
return new Asn1Boolean(stream, length);
|
||||
|
||||
case Asn1Integer.Tag:
|
||||
return new Asn1Integer(stream, length);
|
||||
|
||||
case Asn1OctetString.Tag:
|
||||
return new Asn1OctetString(stream, length);
|
||||
|
||||
case Asn1Enumerated.Tag:
|
||||
return new Asn1Enumerated(stream, length);
|
||||
|
||||
case Asn1Null.Tag:
|
||||
return new Asn1Null(); // has no content to decode.
|
||||
|
||||
default:
|
||||
throw new EndOfStreamException("Unknown tag");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a boolean directly from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>
|
||||
/// Decoded boolean object.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">LBER: BOOLEAN: decode error: EOF.</exception>
|
||||
public static bool DecodeBoolean(Stream stream, int len)
|
||||
{
|
||||
var lber = new sbyte[len];
|
||||
|
||||
if (stream.ReadInput(ref lber, 0, lber.Length) != len)
|
||||
throw new EndOfStreamException("LBER: BOOLEAN: decode error: EOF");
|
||||
|
||||
return lber[0] != 0x00;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a Numeric type directly from a stream. Decodes INTEGER
|
||||
/// and ENUMERATED types.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>
|
||||
/// Decoded numeric object.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">
|
||||
/// LBER: NUMERIC: decode error: EOF
|
||||
/// or
|
||||
/// LBER: NUMERIC: decode error: EOF.
|
||||
/// </exception>
|
||||
public static long DecodeNumeric(Stream stream, int len)
|
||||
{
|
||||
long l = 0;
|
||||
var r = stream.ReadByte();
|
||||
|
||||
if (r < 0)
|
||||
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
|
||||
|
||||
if ((r & 0x80) != 0)
|
||||
{
|
||||
// check for negative number
|
||||
l = -1;
|
||||
}
|
||||
|
||||
l = (l << 8) | r;
|
||||
|
||||
for (var i = 1; i < len; i++)
|
||||
{
|
||||
r = stream.ReadByte();
|
||||
if (r < 0)
|
||||
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
|
||||
|
||||
l = (l << 8) | r;
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode an OctetString directly from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>Decoded octet. </returns>
|
||||
public static object DecodeOctetString(Stream stream, int len)
|
||||
{
|
||||
var octets = new sbyte[len];
|
||||
var totalLen = 0;
|
||||
|
||||
while (totalLen < len)
|
||||
{
|
||||
// Make sure we have read all the data
|
||||
totalLen += stream.ReadInput(ref octets, totalLen, len - totalLen);
|
||||
}
|
||||
|
||||
return octets;
|
||||
}
|
||||
}
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
/// <returns>
|
||||
/// Decoded Asn1Obect.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">Unknown tag.</exception>
|
||||
public static Asn1Object Decode(Stream stream, Int32[] len) {
|
||||
Asn1Identifier asn1Id = new Asn1Identifier(stream);
|
||||
Asn1Length asn1Len = new Asn1Length(stream);
|
||||
|
||||
Int32 length = asn1Len.Length;
|
||||
len[0] = asn1Id.EncodedLength + asn1Len.EncodedLength + length;
|
||||
|
||||
if(asn1Id.Universal == false) {
|
||||
return new Asn1Tagged(stream, length, (Asn1Identifier)asn1Id.Clone());
|
||||
}
|
||||
|
||||
switch(asn1Id.Tag) {
|
||||
case Asn1Sequence.Tag:
|
||||
return new Asn1Sequence(stream, length);
|
||||
|
||||
case Asn1Set.Tag:
|
||||
return new Asn1Set(stream, length);
|
||||
|
||||
case Asn1Boolean.Tag:
|
||||
return new Asn1Boolean(stream, length);
|
||||
|
||||
case Asn1Integer.Tag:
|
||||
return new Asn1Integer(stream, length);
|
||||
|
||||
case Asn1OctetString.Tag:
|
||||
return new Asn1OctetString(stream, length);
|
||||
|
||||
case Asn1Enumerated.Tag:
|
||||
return new Asn1Enumerated(stream, length);
|
||||
|
||||
case Asn1Null.Tag:
|
||||
return new Asn1Null(); // has no content to decode.
|
||||
|
||||
default:
|
||||
throw new EndOfStreamException("Unknown tag");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a boolean directly from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>
|
||||
/// Decoded boolean object.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">LBER: BOOLEAN: decode error: EOF.</exception>
|
||||
public static Boolean DecodeBoolean(Stream stream, Int32 len) {
|
||||
SByte[] lber = new SByte[len];
|
||||
|
||||
if(stream.ReadInput(ref lber, 0, lber.Length) != len) {
|
||||
throw new EndOfStreamException("LBER: BOOLEAN: decode error: EOF");
|
||||
}
|
||||
|
||||
return lber[0] != 0x00;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a Numeric type directly from a stream. Decodes INTEGER
|
||||
/// and ENUMERATED types.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>
|
||||
/// Decoded numeric object.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">
|
||||
/// LBER: NUMERIC: decode error: EOF
|
||||
/// or
|
||||
/// LBER: NUMERIC: decode error: EOF.
|
||||
/// </exception>
|
||||
public static Int64 DecodeNumeric(Stream stream, Int32 len) {
|
||||
Int64 l = 0;
|
||||
Int32 r = stream.ReadByte();
|
||||
|
||||
if(r < 0) {
|
||||
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
|
||||
}
|
||||
|
||||
if((r & 0x80) != 0) {
|
||||
// check for negative number
|
||||
l = -1;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
l = (l << 8) | r;
|
||||
#pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
|
||||
for(Int32 i = 1; i < len; i++) {
|
||||
r = stream.ReadByte();
|
||||
if(r < 0) {
|
||||
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
|
||||
}
|
||||
|
||||
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
l = (l << 8) | r;
|
||||
#pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode an OctetString directly from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>Decoded octet. </returns>
|
||||
public static Object DecodeOctetString(Stream stream, Int32 len) {
|
||||
SByte[] octets = new SByte[len];
|
||||
Int32 totalLen = 0;
|
||||
|
||||
while(totalLen < len) {
|
||||
// Make sure we have read all the data
|
||||
totalLen += stream.ReadInput(ref octets, totalLen, len - totalLen);
|
||||
}
|
||||
|
||||
return octets;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,246 +1,223 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
using System;
|
||||
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>
|
||||
/// 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.
|
||||
/// BER Encode an Asn1Boolean directly into the specified output stream.
|
||||
/// </summary>
|
||||
internal static class LberEncoder
|
||||
{
|
||||
/// <summary>
|
||||
/// BER Encode an Asn1Boolean directly into the specified output stream.
|
||||
/// </summary>
|
||||
/// <param name="b">The Asn1Boolean object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Boolean b, Stream stream)
|
||||
{
|
||||
Encode(b.GetIdentifier(), stream);
|
||||
stream.WriteByte(0x01);
|
||||
stream.WriteByte((byte) (b.BooleanValue() ? 0xff : 0x00));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Numeric directly into the specified outputstream.
|
||||
/// Use a two's complement representation in the fewest number of octets
|
||||
/// possible.
|
||||
/// Can be used to encode INTEGER and ENUMERATED values.
|
||||
/// </summary>
|
||||
/// <param name="n">The Asn1Numeric object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Numeric n, Stream stream)
|
||||
{
|
||||
var octets = new sbyte[8];
|
||||
sbyte len;
|
||||
var longValue = n.LongValue();
|
||||
long endValue = longValue < 0 ? -1 : 0;
|
||||
var endSign = endValue & 0x80;
|
||||
|
||||
for (len = 0; len == 0 || longValue != endValue || (octets[len - 1] & 0x80) != endSign; len++)
|
||||
{
|
||||
octets[len] = (sbyte)(longValue & 0xFF);
|
||||
longValue >>= 8;
|
||||
}
|
||||
|
||||
Encode(n.GetIdentifier(), stream);
|
||||
stream.WriteByte((byte)len);
|
||||
|
||||
for (var i = len - 1; i >= 0; i--)
|
||||
{
|
||||
stream.WriteByte((byte) octets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1OctetString directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="os">The Asn1OctetString object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1OctetString os, Stream stream)
|
||||
{
|
||||
Encode(os.GetIdentifier(), stream);
|
||||
EncodeLength(os.ByteValue().Length, stream);
|
||||
var tempSbyteArray = os.ByteValue();
|
||||
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
|
||||
}
|
||||
|
||||
public static void Encode(Asn1Object obj, Stream stream)
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
case Asn1Boolean b:
|
||||
Encode(b, stream);
|
||||
break;
|
||||
case Asn1Numeric n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Null n:
|
||||
Encode(n.GetIdentifier(), stream);
|
||||
stream.WriteByte(0x00); // Length (with no Content)
|
||||
break;
|
||||
case Asn1OctetString n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Structured n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Tagged n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Choice n:
|
||||
Encode(n.ChoiceValue, stream);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Structured into the specified outputstream. This method
|
||||
/// can be used to encode SET, SET_OF, SEQUENCE, SEQUENCE_OF.
|
||||
/// </summary>
|
||||
/// <param name="c">The Asn1Structured object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Structured c, Stream stream)
|
||||
{
|
||||
Encode(c.GetIdentifier(), stream);
|
||||
|
||||
var arrayValue = c.ToArray();
|
||||
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
foreach (var obj in arrayValue)
|
||||
{
|
||||
Encode(obj, output);
|
||||
}
|
||||
|
||||
EncodeLength((int) output.Length, stream);
|
||||
|
||||
var tempSbyteArray = output.ToArray();
|
||||
stream.Write(tempSbyteArray, 0, tempSbyteArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Tagged directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="t">The Asn1Tagged object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Tagged t, Stream stream)
|
||||
{
|
||||
if (!t.Explicit)
|
||||
{
|
||||
Encode(t.TaggedValue, stream);
|
||||
return;
|
||||
}
|
||||
|
||||
Encode(t.GetIdentifier(), stream);
|
||||
|
||||
// determine the encoded length of the base type.
|
||||
using (var encodedContent = new MemoryStream())
|
||||
{
|
||||
Encode(t.TaggedValue, encodedContent);
|
||||
|
||||
EncodeLength((int) encodedContent.Length, stream);
|
||||
var tempSbyteArray = encodedContent.ToArray().ToSByteArray();
|
||||
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Identifier directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="id">The Asn1Identifier object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Identifier id, Stream stream)
|
||||
{
|
||||
var c = (int) id.Asn1Class;
|
||||
var t = id.Tag;
|
||||
var ccf = (sbyte)((c << 6) | (id.Constructed ? 0x20 : 0));
|
||||
|
||||
if (t < 30)
|
||||
{
|
||||
stream.WriteByte((byte)(ccf | t));
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteByte((byte)(ccf | 0x1F));
|
||||
EncodeTagInteger(t, stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
private static void EncodeLength(int length, Stream stream)
|
||||
{
|
||||
if (length < 0x80)
|
||||
{
|
||||
stream.WriteByte((byte)length);
|
||||
}
|
||||
else
|
||||
{
|
||||
var octets = new sbyte[4]; // 4 bytes sufficient for 32 bit int.
|
||||
sbyte n;
|
||||
for (n = 0; length != 0; n++)
|
||||
{
|
||||
octets[n] = (sbyte)(length & 0xFF);
|
||||
length >>= 8;
|
||||
}
|
||||
|
||||
stream.WriteByte((byte)(0x80 | n));
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
/// <param name="b">The Asn1Boolean object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Boolean b, Stream stream) {
|
||||
Encode(b.GetIdentifier(), stream);
|
||||
stream.WriteByte(0x01);
|
||||
stream.WriteByte((Byte)(b.BooleanValue() ? 0xff : 0x00));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Numeric directly into the specified outputstream.
|
||||
/// Use a two's complement representation in the fewest number of octets
|
||||
/// possible.
|
||||
/// Can be used to encode INTEGER and ENUMERATED values.
|
||||
/// </summary>
|
||||
/// <param name="n">The Asn1Numeric object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Numeric n, Stream stream) {
|
||||
SByte[] octets = new SByte[8];
|
||||
SByte len;
|
||||
Int64 longValue = n.LongValue();
|
||||
Int64 endValue = longValue < 0 ? -1 : 0;
|
||||
Int64 endSign = endValue & 0x80;
|
||||
|
||||
for(len = 0; len == 0 || longValue != endValue || (octets[len - 1] & 0x80) != endSign; len++) {
|
||||
octets[len] = (SByte)(longValue & 0xFF);
|
||||
longValue >>= 8;
|
||||
}
|
||||
|
||||
Encode(n.GetIdentifier(), stream);
|
||||
stream.WriteByte((Byte)len);
|
||||
|
||||
for(Int32 i = len - 1; i >= 0; i--) {
|
||||
stream.WriteByte((Byte)octets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1OctetString directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="os">The Asn1OctetString object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1OctetString os, Stream stream) {
|
||||
Encode(os.GetIdentifier(), stream);
|
||||
EncodeLength(os.ByteValue().Length, stream);
|
||||
SByte[] tempSbyteArray = os.ByteValue();
|
||||
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
|
||||
}
|
||||
|
||||
public static void Encode(Asn1Object obj, Stream stream) {
|
||||
switch(obj) {
|
||||
case Asn1Boolean b:
|
||||
Encode(b, stream);
|
||||
break;
|
||||
case Asn1Numeric n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Null n:
|
||||
Encode(n.GetIdentifier(), stream);
|
||||
stream.WriteByte(0x00); // Length (with no Content)
|
||||
break;
|
||||
case Asn1OctetString n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Structured n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Tagged n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Choice n:
|
||||
Encode(n.ChoiceValue, stream);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Structured into the specified outputstream. This method
|
||||
/// can be used to encode SET, SET_OF, SEQUENCE, SEQUENCE_OF.
|
||||
/// </summary>
|
||||
/// <param name="c">The Asn1Structured object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Structured c, Stream stream) {
|
||||
Encode(c.GetIdentifier(), stream);
|
||||
|
||||
Asn1Object[] arrayValue = c.ToArray();
|
||||
|
||||
using(MemoryStream output = new MemoryStream()) {
|
||||
foreach(Asn1Object obj in arrayValue) {
|
||||
Encode(obj, output);
|
||||
}
|
||||
|
||||
EncodeLength((Int32)output.Length, stream);
|
||||
|
||||
Byte[] tempSbyteArray = output.ToArray();
|
||||
stream.Write(tempSbyteArray, 0, tempSbyteArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Tagged directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="t">The Asn1Tagged object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Tagged t, Stream stream) {
|
||||
if(!t.Explicit) {
|
||||
Encode(t.TaggedValue, stream);
|
||||
return;
|
||||
}
|
||||
|
||||
Encode(t.GetIdentifier(), stream);
|
||||
|
||||
// determine the encoded length of the base type.
|
||||
using(MemoryStream encodedContent = new MemoryStream()) {
|
||||
Encode(t.TaggedValue, encodedContent);
|
||||
|
||||
EncodeLength((Int32)encodedContent.Length, stream);
|
||||
SByte[] tempSbyteArray = encodedContent.ToArray().ToSByteArray();
|
||||
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Identifier directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="id">The Asn1Identifier object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Identifier id, Stream stream) {
|
||||
Int32 c = (Int32)id.Asn1Class;
|
||||
Int32 t = id.Tag;
|
||||
SByte ccf = (SByte)((c << 6) | (id.Constructed ? 0x20 : 0));
|
||||
|
||||
if(t < 30) {
|
||||
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
stream.WriteByte((Byte)(ccf | t));
|
||||
#pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
} else {
|
||||
stream.WriteByte((Byte)(ccf | 0x1F));
|
||||
EncodeTagInteger(t, stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
private static void EncodeLength(Int32 length, Stream stream) {
|
||||
if(length < 0x80) {
|
||||
stream.WriteByte((Byte)length);
|
||||
} else {
|
||||
SByte[] octets = new SByte[4]; // 4 bytes sufficient for 32 bit int.
|
||||
SByte n;
|
||||
for(n = 0; length != 0; n++) {
|
||||
octets[n] = (SByte)(length & 0xFF);
|
||||
length >>= 8;
|
||||
}
|
||||
|
||||
stream.WriteByte((Byte)(0x80 | n));
|
||||
|
||||
for(Int32 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(Int32 val, Stream stream) {
|
||||
SByte[] octets = new SByte[5];
|
||||
Int32 n;
|
||||
|
||||
for(n = 0; val != 0; n++) {
|
||||
octets[n] = (SByte)(val & 0x7F);
|
||||
val >>= 7;
|
||||
}
|
||||
|
||||
for(Int32 i = n - 1; i > 0; i--) {
|
||||
stream.WriteByte((Byte)(octets[i] | 0x80));
|
||||
}
|
||||
|
||||
stream.WriteByte((Byte)octets[0]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,402 +1,382 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Exceptions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Unosquare.Swan.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>
|
||||
/// 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.
|
||||
/// Returns the protocol version uses to authenticate.
|
||||
/// 0 is returned if no authentication has been performed.
|
||||
/// </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 int LdapV3 = 3;
|
||||
|
||||
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
|
||||
|
||||
private Connection _conn;
|
||||
private bool _isDisposing;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the protocol version uses to authenticate.
|
||||
/// 0 is returned if no authentication has been performed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The protocol version.
|
||||
/// </value>
|
||||
public int ProtocolVersion => BindProperties?.ProtocolVersion ?? LdapV3;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the distinguished name (DN) used for as the bind name during
|
||||
/// the last successful bind operation. null is returned
|
||||
/// if no authentication has been performed or if the bind resulted in
|
||||
/// an anonymous connection.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication dn.
|
||||
/// </value>
|
||||
public string AuthenticationDn => BindProperties == null ? null : (BindProperties.Anonymous ? null : BindProperties.AuthenticationDN);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the method used to authenticate the connection. The return
|
||||
/// value is one of the following:.
|
||||
/// <ul><li>"none" indicates the connection is not authenticated.</li><li>
|
||||
/// "simple" indicates simple authentication was used or that a null
|
||||
/// or empty authentication DN was specified.
|
||||
/// </li><li>"sasl" indicates that a SASL mechanism was used to authenticate</li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication method.
|
||||
/// </value>
|
||||
public string AuthenticationMethod => BindProperties == null ? "simple" : BindProperties.AuthenticationMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the connection represented by this object is open
|
||||
/// at this time.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if connection is open; false if the connection is closed.
|
||||
/// </returns>
|
||||
public bool Connected => _conn?.IsConnected == true;
|
||||
|
||||
internal BindProperties BindProperties { get; set; }
|
||||
|
||||
internal List<RfcLdapMessage> Messages { get; } = new List<RfcLdapMessage>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposing) return;
|
||||
|
||||
_isDisposing = true;
|
||||
Disconnect();
|
||||
_cts?.Dispose();
|
||||
}
|
||||
|
||||
/// <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="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);
|
||||
|
||||
/// <value>
|
||||
/// The protocol version.
|
||||
/// </value>
|
||||
public Int32 ProtocolVersion => this.BindProperties?.ProtocolVersion ?? LdapV3;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the distinguished name (DN) used for as the bind name during
|
||||
/// the last successful bind operation. null is returned
|
||||
/// if no authentication has been performed or if the bind resulted in
|
||||
/// an anonymous connection.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication dn.
|
||||
/// </value>
|
||||
public String AuthenticationDn => this.BindProperties == null ? null : (this.BindProperties.Anonymous ? null : this.BindProperties.AuthenticationDN);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the method used to authenticate the connection. The return
|
||||
/// value is one of the following:.
|
||||
/// <ul><li>"none" indicates the connection is not authenticated.</li><li>
|
||||
/// "simple" indicates simple authentication was used or that a null
|
||||
/// or empty authentication DN was specified.
|
||||
/// </li><li>"sasl" indicates that a SASL mechanism was used to authenticate</li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication method.
|
||||
/// </value>
|
||||
public String AuthenticationMethod => this.BindProperties == null ? "simple" : this.BindProperties.AuthenticationMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the connection represented by this object is open
|
||||
/// at this time.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if connection is open; false if the connection is closed.
|
||||
/// </returns>
|
||||
public Boolean Connected => this._conn?.IsConnected == true;
|
||||
|
||||
internal BindProperties BindProperties {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal List<RfcLdapMessage> Messages { get; } = new List<RfcLdapMessage>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(this._isDisposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposing = true;
|
||||
this.Disconnect();
|
||||
this._cts?.Dispose();
|
||||
}
|
||||
|
||||
/// <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="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) => this.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(Int32 version, String dn, String password) {
|
||||
dn = String.IsNullOrEmpty(dn) ? String.Empty : dn.Trim();
|
||||
SByte[] passwordData = String.IsNullOrWhiteSpace(password) ? new SByte[] { } : Encoding.UTF8.GetSBytes(password);
|
||||
|
||||
Boolean anonymous = false;
|
||||
|
||||
if(passwordData.Length == 0) {
|
||||
anonymous = true; // anonymous, password length zero with simple bind
|
||||
dn = String.Empty; // set to null if anonymous
|
||||
}
|
||||
|
||||
this.BindProperties = new BindProperties(version, dn, "simple", anonymous);
|
||||
|
||||
return this.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, Int32 port) {
|
||||
TcpClient tcpClient = new TcpClient();
|
||||
await tcpClient.ConnectAsync(host, port).ConfigureAwait(false);
|
||||
this._conn = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 0);
|
||||
|
||||
#pragma warning disable 4014
|
||||
Task.Run(() => RetrieveMessages(), _cts.Token);
|
||||
_ = Task.Run(() => this.RetrieveMessages(), this._cts.Token);
|
||||
#pragma warning restore 4014
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously disconnects from the Ldap server.
|
||||
/// Before the object can perform Ldap operations again, it must
|
||||
/// reconnect to the server by calling connect.
|
||||
/// The disconnect method abandons any outstanding requests, issues an
|
||||
/// unbind request to the server, and then closes the socket.
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
// disconnect from API call
|
||||
_cts.Cancel();
|
||||
_conn.Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously reads the entry for the specified distinguished name (DN),
|
||||
/// using the specified constraints, and retrieves only the specified
|
||||
/// attributes from the entry.
|
||||
/// </summary>
|
||||
/// <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="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// the LdapEntry read from the server.
|
||||
/// </returns>
|
||||
/// <exception cref="LdapException">Read response is ambiguous, multiple entries returned.</exception>
|
||||
public async Task<LdapEntry> Read(string dn, string[] attrs = null, CancellationToken ct = default)
|
||||
{
|
||||
var sr = await Search(dn, LdapScope.ScopeSub, null, attrs, false, ct);
|
||||
LdapEntry ret = null;
|
||||
|
||||
if (sr.HasMore())
|
||||
{
|
||||
ret = sr.Next();
|
||||
if (sr.HasMore())
|
||||
{
|
||||
throw new LdapException("Read response is ambiguous, multiple entries returned", LdapStatusCode.AmbiguousResponse);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// wait for search results).
|
||||
/// </summary>
|
||||
/// <param name="base">The base distinguished name to search from.</param>
|
||||
/// <param name="scope">The scope of the entries to search.</param>
|
||||
/// <param name="filter">The search filter specifying the search criteria.</param>
|
||||
/// <param name="attrs">The names of attributes to retrieve.</param>
|
||||
/// <param name="typesOnly">If true, returns the names but not the values of
|
||||
/// the attributes found. If false, returns the
|
||||
/// names and values for attributes found.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
public async Task<LdapSearchResults> Search(
|
||||
string @base,
|
||||
LdapScope scope,
|
||||
string filter = "objectClass=*",
|
||||
string[] attrs = null,
|
||||
bool typesOnly = false,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// TODO: Add Search options
|
||||
var msg = new LdapSearchRequest(@base, scope, filter, attrs, 0, 1000, 0, typesOnly, null);
|
||||
|
||||
await RequestLdapMessage(msg, ct).ConfigureAwait(false);
|
||||
|
||||
return new LdapSearchResults(Messages, msg.MessageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the specified dn.
|
||||
/// </summary>
|
||||
/// <param name="distinguishedName">Name of the distinguished.</param>
|
||||
/// <param name="mods">The mods.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">distinguishedName.</exception>
|
||||
public Task Modify(string distinguishedName, LdapModification[] mods, CancellationToken ct = default)
|
||||
{
|
||||
if (distinguishedName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(distinguishedName));
|
||||
}
|
||||
|
||||
return RequestLdapMessage(new LdapModifyRequest(distinguishedName, mods, null), ct);
|
||||
}
|
||||
|
||||
internal async Task RequestLdapMessage(LdapMessage msg, CancellationToken ct = default)
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
LberEncoder.Encode(msg.Asn1Object, stream);
|
||||
await _conn.WriteDataAsync(stream.ToArray(), true, ct).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
while (new List<RfcLdapMessage>(Messages).Any(x => x.MessageId == msg.MessageId) == false)
|
||||
await Task.Delay(100, ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
|
||||
var first = new List<RfcLdapMessage>(Messages).FirstOrDefault(x => x.MessageId == msg.MessageId);
|
||||
|
||||
if (first != null)
|
||||
{
|
||||
var response = new LdapResponse(first);
|
||||
response.ChkResultCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RetrieveMessages()
|
||||
{
|
||||
while (!_cts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var asn1Id = new Asn1Identifier(_conn.ActiveStream);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously disconnects from the Ldap server.
|
||||
/// Before the object can perform Ldap operations again, it must
|
||||
/// reconnect to the server by calling connect.
|
||||
/// The disconnect method abandons any outstanding requests, issues an
|
||||
/// unbind request to the server, and then closes the socket.
|
||||
/// </summary>
|
||||
public void Disconnect() {
|
||||
// disconnect from API call
|
||||
this._cts.Cancel();
|
||||
this._conn.Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously reads the entry for the specified distinguished name (DN),
|
||||
/// using the specified constraints, and retrieves only the specified
|
||||
/// attributes from the entry.
|
||||
/// </summary>
|
||||
/// <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="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// the LdapEntry read from the server.
|
||||
/// </returns>
|
||||
/// <exception cref="LdapException">Read response is ambiguous, multiple entries returned.</exception>
|
||||
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;
|
||||
|
||||
if(sr.HasMore()) {
|
||||
ret = sr.Next();
|
||||
if(sr.HasMore()) {
|
||||
throw new LdapException("Read response is ambiguous, multiple entries returned", LdapStatusCode.AmbiguousResponse);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// wait for search results).
|
||||
/// </summary>
|
||||
/// <param name="base">The base distinguished name to search from.</param>
|
||||
/// <param name="scope">The scope of the entries to search.</param>
|
||||
/// <param name="filter">The search filter specifying the search criteria.</param>
|
||||
/// <param name="attrs">The names of attributes to retrieve.</param>
|
||||
/// <param name="typesOnly">If true, returns the names but not the values of
|
||||
/// the attributes found. If false, returns the
|
||||
/// names and values for attributes found.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
public async Task<LdapSearchResults> Search(
|
||||
String @base,
|
||||
LdapScope scope,
|
||||
String filter = "objectClass=*",
|
||||
String[] attrs = null,
|
||||
Boolean typesOnly = false,
|
||||
CancellationToken ct = default) {
|
||||
// TODO: Add Search options
|
||||
LdapSearchRequest msg = new LdapSearchRequest(@base, scope, filter, attrs, 0, 1000, 0, typesOnly, null);
|
||||
|
||||
await this.RequestLdapMessage(msg, ct).ConfigureAwait(false);
|
||||
|
||||
return new LdapSearchResults(this.Messages, msg.MessageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the specified dn.
|
||||
/// </summary>
|
||||
/// <param name="distinguishedName">Name of the distinguished.</param>
|
||||
/// <param name="mods">The mods.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">distinguishedName.</exception>
|
||||
public Task Modify(String distinguishedName, LdapModification[] mods, CancellationToken ct = default) {
|
||||
if(distinguishedName == null) {
|
||||
throw new ArgumentNullException(nameof(distinguishedName));
|
||||
}
|
||||
|
||||
return this.RequestLdapMessage(new LdapModifyRequest(distinguishedName, mods, null), ct);
|
||||
}
|
||||
|
||||
internal async Task RequestLdapMessage(LdapMessage msg, CancellationToken ct = default) {
|
||||
using(MemoryStream stream = new MemoryStream()) {
|
||||
LberEncoder.Encode(msg.Asn1Object, stream);
|
||||
await this._conn.WriteDataAsync(stream.ToArray(), true, ct).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
while(new List<RfcLdapMessage>(this.Messages).Any(x => x.MessageId == msg.MessageId) == false) {
|
||||
await Task.Delay(100, ct).ConfigureAwait(false);
|
||||
}
|
||||
} catch(ArgumentException) {
|
||||
// expected
|
||||
}
|
||||
|
||||
RfcLdapMessage first = new List<RfcLdapMessage>(this.Messages).FirstOrDefault(x => x.MessageId == msg.MessageId);
|
||||
|
||||
if(first != null) {
|
||||
LdapResponse response = new LdapResponse(first);
|
||||
response.ChkResultCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RetrieveMessages() {
|
||||
while(!this._cts.IsCancellationRequested) {
|
||||
try {
|
||||
Asn1Identifier asn1Id = new Asn1Identifier(this._conn.ActiveStream);
|
||||
|
||||
if(asn1Id.Tag != Asn1Sequence.Tag) {
|
||||
continue; // loop looking for an RfcLdapMessage identifier
|
||||
}
|
||||
|
||||
// Turn the message into an RfcMessage class
|
||||
Asn1Length asn1Len = new Asn1Length(this._conn.ActiveStream);
|
||||
|
||||
this.Messages.Add(new RfcLdapMessage(this._conn.ActiveStream, asn1Len.Length));
|
||||
} catch(IOException) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once FunctionNeverReturns
|
||||
}
|
||||
}
|
||||
}
|
@ -1,292 +1,275 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Exceptions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unosquare.Swan.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>
|
||||
/// 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.
|
||||
/// Initializes a new instance of the <see cref="LdapControl"/> class.
|
||||
/// Constructs a new LdapControl object using the specified values.
|
||||
/// </summary>
|
||||
public class LdapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapControl"/> class.
|
||||
/// Constructs a new LdapControl object using the specified values.
|
||||
/// </summary>
|
||||
/// <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
|
||||
/// the control is not supported. False if
|
||||
/// 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>
|
||||
public LdapControl(string oid, bool critical, sbyte[] values)
|
||||
{
|
||||
if (oid == null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// the control is not supported. False if
|
||||
/// 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>
|
||||
public LdapControl(String oid, Boolean critical, SByte[] values) {
|
||||
if(oid == null) {
|
||||
throw new ArgumentException("An OID must be specified");
|
||||
}
|
||||
|
||||
this.Asn1Object = new RfcControl(
|
||||
oid,
|
||||
new Asn1Boolean(critical),
|
||||
values == null ? null : new Asn1OctetString(values));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a simple bind request.
|
||||
/// Returns the identifier of the control.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
public class LdapBindRequest : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 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();
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The identifier.
|
||||
/// </value>
|
||||
public String Id => this.Asn1Object.ControlType.StringValue();
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates a continuation reference from an asynchronous search operation.
|
||||
/// Returns whether the control is critical for the 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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// <c>true</c> if critical; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean Critical => this.Asn1Object.Criticality.BooleanValue();
|
||||
|
||||
internal static RespControlVector RegisteredControls { get; } = new RespControlVector(5);
|
||||
|
||||
internal RfcControl Asn1Object {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// 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>
|
||||
internal class RespControlVector : List<RespControlVector.RegisteredControl>
|
||||
{
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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
|
||||
{
|
||||
private readonly sbyte[] _password;
|
||||
private static readonly Asn1Identifier Id = new Asn1Identifier(LdapOperation.BindRequest);
|
||||
|
||||
public RfcBindRequest(int version, string name, sbyte[] password)
|
||||
: base(3)
|
||||
{
|
||||
_password = password;
|
||||
Add(new Asn1Integer(version));
|
||||
Add(name);
|
||||
Add(new RfcAuthenticationChoice(password));
|
||||
}
|
||||
|
||||
public Asn1Integer Version
|
||||
{
|
||||
get => (Asn1Integer)Get(0);
|
||||
set => Set(0, value);
|
||||
}
|
||||
|
||||
public Asn1OctetString Name
|
||||
{
|
||||
get => (Asn1OctetString)Get(1);
|
||||
set => Set(1, value);
|
||||
}
|
||||
|
||||
public RfcAuthenticationChoice AuthenticationChoice
|
||||
{
|
||||
get => (RfcAuthenticationChoice)Get(2);
|
||||
set => Set(2, value);
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => Id;
|
||||
|
||||
public string GetRequestDN() => ((Asn1OctetString)Get(1)).StringValue();
|
||||
}
|
||||
/// 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() => this.Asn1Object.ControlValue?.ByteValue();
|
||||
|
||||
internal void SetValue(SByte[] controlValue) => this.Asn1Object.ControlValue = new Asn1OctetString(controlValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a simple bind request.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
public class LdapBindRequest : LdapMessage {
|
||||
/// <summary>
|
||||
/// 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(Int32 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 => this.Asn1Object.RequestDn;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override String ToString() => this.Asn1Object.ToString();
|
||||
}
|
||||
|
||||
/// <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
@ -1,135 +1,130 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Ldap Modification Operators.
|
||||
/// </summary>
|
||||
public enum LdapModificationOp {
|
||||
/// <summary>
|
||||
/// Ldap Modification Operators.
|
||||
/// Adds the listed values to the given attribute, creating
|
||||
/// the attribute if it does not already exist.
|
||||
/// </summary>
|
||||
public enum LdapModificationOp
|
||||
{
|
||||
/// <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,
|
||||
}
|
||||
|
||||
Add = 0,
|
||||
|
||||
/// <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>
|
||||
public enum LdapScope
|
||||
{
|
||||
/// <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,
|
||||
}
|
||||
|
||||
Delete = 1,
|
||||
|
||||
/// <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>
|
||||
internal enum SubstringOp
|
||||
{
|
||||
/// <summary>
|
||||
/// Search Filter Identifier for an INITIAL component of a SUBSTRING.
|
||||
/// Note: An initial SUBSTRING is represented as "value*".
|
||||
/// </summary>
|
||||
Initial = 0,
|
||||
|
||||
/// <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,
|
||||
}
|
||||
|
||||
Replace = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// LDAP valid scopes.
|
||||
/// </summary>
|
||||
public enum LdapScope {
|
||||
/// <summary>
|
||||
/// Filtering Operators.
|
||||
/// Used with search to specify that the scope of entrys to search is to
|
||||
/// search only the base object.
|
||||
/// </summary>
|
||||
internal enum FilterOp
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifier for AND component.
|
||||
/// </summary>
|
||||
And = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for OR component.
|
||||
/// </summary>
|
||||
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,
|
||||
}
|
||||
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>
|
||||
/// Substring Operators.
|
||||
/// </summary>
|
||||
internal enum SubstringOp {
|
||||
/// <summary>
|
||||
/// Search Filter Identifier for an INITIAL component of a SUBSTRING.
|
||||
/// Note: An initial SUBSTRING is represented as "value*".
|
||||
/// </summary>
|
||||
Initial = 0,
|
||||
|
||||
/// <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>
|
||||
/// Filtering Operators.
|
||||
/// </summary>
|
||||
internal enum FilterOp {
|
||||
/// <summary>
|
||||
/// Identifier for AND component.
|
||||
/// </summary>
|
||||
And = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for OR component.
|
||||
/// </summary>
|
||||
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,
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// The base class for Ldap request and response messages.
|
||||
/// Subclassed by response messages used in asynchronous operations.
|
||||
/// 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>
|
||||
public class LdapMessage
|
||||
{
|
||||
internal RfcLdapMessage Message;
|
||||
|
||||
private int _imsgNum = -1; // This instance LdapMessage number
|
||||
|
||||
private LdapOperation _messageType = LdapOperation.Unknown;
|
||||
|
||||
private string _stringTag;
|
||||
|
||||
internal LdapMessage()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="op">The operation type of message.</param>
|
||||
/// <param name="controls">The controls to use with the operation.</param>
|
||||
/// <seealso cref="Type"></seealso>
|
||||
internal LdapMessage(LdapOperation type, IRfcRequest op, LdapControl[] controls = null)
|
||||
{
|
||||
// Get a unique number for this request message
|
||||
_messageType = type;
|
||||
RfcControls asn1Ctrls = null;
|
||||
|
||||
if (controls != null)
|
||||
{
|
||||
// Move LdapControls into an RFC 2251 Controls object.
|
||||
asn1Ctrls = new RfcControls();
|
||||
|
||||
foreach (var t in controls)
|
||||
{
|
||||
asn1Ctrls.Add(t.Asn1Object);
|
||||
}
|
||||
}
|
||||
|
||||
// create RFC 2251 LdapMessage
|
||||
Message = new RfcLdapMessage(op, asn1Ctrls);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="message">A response message.</param>
|
||||
internal LdapMessage(RfcLdapMessage message) => Message = message;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the message ID. The message ID is an integer value
|
||||
/// identifying the Ldap request and its response.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The message identifier.
|
||||
/// </value>
|
||||
public virtual int MessageId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_imsgNum == -1)
|
||||
{
|
||||
_imsgNum = Message.MessageId;
|
||||
}
|
||||
|
||||
return _imsgNum;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the message is a request or a response.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if request; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public virtual bool Request => Message.IsRequest();
|
||||
|
||||
internal LdapOperation Type
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_messageType == LdapOperation.Unknown)
|
||||
{
|
||||
_messageType = Message.Type;
|
||||
}
|
||||
|
||||
return _messageType;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual RfcLdapMessage Asn1Object => 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}";
|
||||
}
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="op">The operation type of message.</param>
|
||||
/// <param name="controls">The controls to use with the operation.</param>
|
||||
/// <seealso cref="Type"></seealso>
|
||||
internal LdapMessage(LdapOperation type, IRfcRequest op, LdapControl[] controls = null) {
|
||||
// Get a unique number for this request message
|
||||
this._messageType = type;
|
||||
RfcControls asn1Ctrls = null;
|
||||
|
||||
if(controls != null) {
|
||||
// Move LdapControls into an RFC 2251 Controls object.
|
||||
asn1Ctrls = new RfcControls();
|
||||
|
||||
foreach(LdapControl t in controls) {
|
||||
asn1Ctrls.Add(t.Asn1Object);
|
||||
}
|
||||
}
|
||||
|
||||
// create RFC 2251 LdapMessage
|
||||
this.Message = new RfcLdapMessage(op, asn1Ctrls);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="message">A response message.</param>
|
||||
internal LdapMessage(RfcLdapMessage message) => this.Message = message;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the message ID. The message ID is an integer value
|
||||
/// identifying the Ldap request and its response.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The message identifier.
|
||||
/// </value>
|
||||
public virtual Int32 MessageId {
|
||||
get {
|
||||
if(this._imsgNum == -1) {
|
||||
this._imsgNum = this.Message.MessageId;
|
||||
}
|
||||
|
||||
return this._imsgNum;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the message is a request or a response.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if request; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public virtual Boolean Request => this.Message.IsRequest();
|
||||
|
||||
internal LdapOperation Type {
|
||||
get {
|
||||
if(this._messageType == LdapOperation.Unknown) {
|
||||
this._messageType = this.Message.Type;
|
||||
}
|
||||
|
||||
return this._messageType;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual RfcLdapMessage Asn1Object => this.Message;
|
||||
|
||||
internal virtual LdapMessage RequestingMessage => this.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 => this._stringTag ?? (this.Request ? null : this.RequestingMessage?._stringTag);
|
||||
|
||||
set => this._stringTag = value;
|
||||
}
|
||||
|
||||
private String Name => this.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() => $"{this.Name}({this.MessageId}): {this.Message}";
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// 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>
|
||||
/// Initializes a new instance of the <see cref="LdapModification" /> class.
|
||||
/// Specifies a modification to be made to an attribute.
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapConnection.Modify"></seealso>
|
||||
/// <seealso cref="LdapAttribute"></seealso>
|
||||
public sealed class LdapModification : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapModification" /> class.
|
||||
/// Specifies a modification to be made to an attribute.
|
||||
/// </summary>
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="attr">The attribute to modify.</param>
|
||||
public LdapModification(LdapModificationOp op, LdapAttribute attr)
|
||||
{
|
||||
Op = op;
|
||||
Attribute = attr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapModification"/> class.
|
||||
/// </summary>
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="attrName">Name of the attribute.</param>
|
||||
/// <param name="attrValue">The attribute value.</param>
|
||||
public LdapModification(LdapModificationOp op, string attrName, string attrValue)
|
||||
: this(op, new LdapAttribute(attrName, attrValue))
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the attribute to modify, with any existing values.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The attribute.
|
||||
/// </value>
|
||||
public LdapAttribute Attribute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of modification specified by this object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The op.
|
||||
/// </value>
|
||||
public LdapModificationOp Op { get; }
|
||||
}
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="attr">The attribute to modify.</param>
|
||||
public LdapModification(LdapModificationOp op, LdapAttribute attr) {
|
||||
this.Op = op;
|
||||
this.Attribute = attr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapModification"/> class.
|
||||
/// </summary>
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="attrName">Name of the attribute.</param>
|
||||
/// <param name="attrValue">The attribute value.</param>
|
||||
public LdapModification(LdapModificationOp op, String attrName, String attrValue)
|
||||
: this(op, new LdapAttribute(attrName, attrValue)) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the attribute to modify, with any existing values.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The attribute.
|
||||
/// </value>
|
||||
public LdapAttribute Attribute {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of modification specified by this object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The op.
|
||||
/// </value>
|
||||
public LdapModificationOp Op {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// Represents a LDAP Modification Request Message.
|
||||
/// Initializes a new instance of the <see cref="LdapModifyRequest"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
public sealed class LdapModifyRequest : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapModifyRequest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dn">The dn.</param>
|
||||
/// <param name="modifications">The modifications.</param>
|
||||
/// <param name="control">The control.</param>
|
||||
public LdapModifyRequest(string dn, LdapModification[] modifications, LdapControl[] control)
|
||||
: base(LdapOperation.ModifyRequest, new RfcModifyRequest(dn, EncodeModifications(modifications)), control)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dn.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dn.
|
||||
/// </value>
|
||||
public string DN => Asn1Object.RequestDn;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Asn1Object.ToString();
|
||||
|
||||
private static Asn1SequenceOf EncodeModifications(LdapModification[] mods)
|
||||
{
|
||||
var rfcMods = new Asn1SequenceOf(mods.Length);
|
||||
|
||||
foreach (var t in mods)
|
||||
{
|
||||
var attr = t.Attribute;
|
||||
|
||||
var vals = new Asn1SetOf(attr.Size());
|
||||
if (attr.Size() > 0)
|
||||
{
|
||||
foreach (var val in attr.ByteValueArray)
|
||||
{
|
||||
vals.Add(new Asn1OctetString(val));
|
||||
}
|
||||
}
|
||||
|
||||
var rfcMod = new Asn1Sequence(2);
|
||||
rfcMod.Add(new Asn1Enumerated((int) t.Op));
|
||||
rfcMod.Add(new RfcAttributeTypeAndValues(attr.Name, vals));
|
||||
|
||||
rfcMods.Add(rfcMod);
|
||||
}
|
||||
|
||||
return rfcMods;
|
||||
}
|
||||
|
||||
internal class RfcAttributeTypeAndValues : Asn1Sequence
|
||||
{
|
||||
public RfcAttributeTypeAndValues(string type, Asn1Object vals)
|
||||
: base(2)
|
||||
{
|
||||
Add(type);
|
||||
Add(vals);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="dn">The dn.</param>
|
||||
/// <param name="modifications">The modifications.</param>
|
||||
/// <param name="control">The control.</param>
|
||||
public LdapModifyRequest(String dn, LdapModification[] modifications, LdapControl[] control)
|
||||
: base(LdapOperation.ModifyRequest, new RfcModifyRequest(dn, EncodeModifications(modifications)), control) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dn.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dn.
|
||||
/// </value>
|
||||
public String DN => this.Asn1Object.RequestDn;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override String ToString() => this.Asn1Object.ToString();
|
||||
|
||||
private static Asn1SequenceOf EncodeModifications(LdapModification[] mods) {
|
||||
Asn1SequenceOf rfcMods = new Asn1SequenceOf(mods.Length);
|
||||
|
||||
foreach(LdapModification t in mods) {
|
||||
LdapAttribute attr = t.Attribute;
|
||||
|
||||
Asn1SetOf vals = new Asn1SetOf(attr.Size());
|
||||
if(attr.Size() > 0) {
|
||||
foreach(SByte[] val in attr.ByteValueArray) {
|
||||
vals.Add(new Asn1OctetString(val));
|
||||
}
|
||||
}
|
||||
|
||||
Asn1Sequence rfcMod = new Asn1Sequence(2);
|
||||
rfcMod.Add(new Asn1Enumerated((Int32)t.Op));
|
||||
rfcMod.Add(new RfcAttributeTypeAndValues(attr.Name, vals));
|
||||
|
||||
rfcMods.Add(rfcMod);
|
||||
}
|
||||
|
||||
return rfcMods;
|
||||
}
|
||||
|
||||
internal class RfcAttributeTypeAndValues : Asn1Sequence {
|
||||
public RfcAttributeTypeAndValues(String type, Asn1Object vals)
|
||||
: base(2) {
|
||||
this.Add(type);
|
||||
this.Add(vals);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,117 +1,114 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// LDAP Operation.
|
||||
/// </summary>
|
||||
internal enum LdapOperation {
|
||||
/// <summary>
|
||||
/// LDAP Operation.
|
||||
/// The unknown
|
||||
/// </summary>
|
||||
internal enum LdapOperation
|
||||
{
|
||||
/// <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,
|
||||
}
|
||||
|
||||
Unknown = -1,
|
||||
|
||||
/// <summary>
|
||||
/// ASN1 tags.
|
||||
/// A bind request operation.
|
||||
/// BIND_REQUEST = 0
|
||||
/// </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,
|
||||
}
|
||||
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>
|
||||
/// 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,
|
||||
}
|
||||
}
|
@ -1,185 +1,180 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.Collections;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search request.
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapMessage" />
|
||||
internal sealed class LdapSearchRequest : LdapMessage {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search request.
|
||||
/// Initializes a new instance of the <see cref="LdapSearchRequest"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapMessage" />
|
||||
internal sealed class LdapSearchRequest : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapSearchRequest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ldapBase">The base distinguished name to search from.</param>
|
||||
/// <param name="scope">The scope of the entries to search. The following
|
||||
/// are the valid options:.
|
||||
/// <ul><li>SCOPE_BASE - searches only the base DN</li><li>SCOPE_ONE - searches only entries under the base DN</li><li>
|
||||
/// SCOPE_SUB - searches the base DN and all entries
|
||||
/// within its subtree
|
||||
/// </li></ul></param>
|
||||
/// <param name="filter">The search filter specifying the search criteria.</param>
|
||||
/// <param name="attrs">The names of attributes to retrieve.
|
||||
/// operation exceeds the time limit.</param>
|
||||
/// <param name="dereference">Specifies when aliases should be dereferenced.
|
||||
/// Must be one of the constants defined in
|
||||
/// LdapConstraints, which are DEREF_NEVER,
|
||||
/// DEREF_FINDING, DEREF_SEARCHING, or DEREF_ALWAYS.</param>
|
||||
/// <param name="maxResults">The maximum number of search results to return
|
||||
/// for a search request.
|
||||
/// The search operation will be terminated by the server
|
||||
/// with an LdapException.SIZE_LIMIT_EXCEEDED if the
|
||||
/// number of results exceed the maximum.</param>
|
||||
/// <param name="serverTimeLimit">The maximum time in seconds that the server
|
||||
/// should spend returning search results. This is a
|
||||
/// server-enforced limit. A value of 0 means
|
||||
/// no time limit.</param>
|
||||
/// <param name="typesOnly">If true, returns the names but not the values of
|
||||
/// the attributes found. If false, returns the
|
||||
/// names and values for attributes found.</param>
|
||||
/// <param name="cont">Any controls that apply to the search request.
|
||||
/// or null if none.</param>
|
||||
/// <seealso cref="LdapConnection.Search"></seealso>
|
||||
public LdapSearchRequest(
|
||||
string ldapBase,
|
||||
LdapScope scope,
|
||||
string filter,
|
||||
string[] attrs,
|
||||
int dereference,
|
||||
int maxResults,
|
||||
int serverTimeLimit,
|
||||
bool typesOnly,
|
||||
LdapControl[] cont)
|
||||
: base(
|
||||
LdapOperation.SearchRequest,
|
||||
new RfcSearchRequest(ldapBase, scope, dereference, maxResults, serverTimeLimit, typesOnly, filter, attrs),
|
||||
cont)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an Iterator object representing the parsed filter for
|
||||
/// this search request.
|
||||
/// 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
|
||||
/// filter.
|
||||
/// Values returned as a byte array may represent UTF-8 characters or may
|
||||
/// be binary values. The possible Integer components of a search filter
|
||||
/// and the associated values that follow are:.
|
||||
/// <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>
|
||||
/// EQUALITY_MATCH - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// GREATER_OR_EQUAL - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// LESS_OR_EQUAL - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// APPROX_MATCH - followed by the attribute name represented as a
|
||||
/// 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>
|
||||
/// EXTENSIBLE_MATCH - followed by the name of the matching rule
|
||||
/// represented as a String, by the attribute name represented
|
||||
/// as a String, and by the attribute value represented as a
|
||||
/// byte array.
|
||||
/// </li><li>
|
||||
/// SUBSTRINGS - followed by the attribute name represented as a
|
||||
/// String, by one or more SUBSTRING components (INITIAL, ANY,
|
||||
/// or FINAL) followed by the SUBSTRING value.
|
||||
/// </li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The search filter.
|
||||
/// </value>
|
||||
public IEnumerator SearchFilter => RfcFilter.GetFilterIterator();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Base DN for a search request.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// the base DN for a search request.
|
||||
/// </returns>
|
||||
public string DN => Asn1Object.RequestDn;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the scope of a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The scope.
|
||||
/// </value>
|
||||
public int Scope => ((Asn1Enumerated)((RfcSearchRequest)Asn1Object.Get(1)).Get(1)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the behaviour of dereferencing aliases on a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dereference.
|
||||
/// </value>
|
||||
public int Dereference => ((Asn1Enumerated)((RfcSearchRequest)Asn1Object.Get(1)).Get(2)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the maximum number of entries to be returned on a search.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The maximum results.
|
||||
/// </value>
|
||||
public int MaxResults => ((Asn1Integer)((RfcSearchRequest)Asn1Object.Get(1)).Get(3)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the server time limit for a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The server time limit.
|
||||
/// </value>
|
||||
public int ServerTimeLimit => ((Asn1Integer)((RfcSearchRequest)Asn1Object.Get(1)).Get(4)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves whether attribute values or only attribute types(names) should
|
||||
/// be returned in a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [types only]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool TypesOnly => ((Asn1Boolean)((RfcSearchRequest)Asn1Object.Get(1)).Get(5)).BooleanValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an array of attribute names to request for in a search.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The attributes.
|
||||
/// </value>
|
||||
public string[] Attributes
|
||||
{
|
||||
get
|
||||
{
|
||||
var attrs = (RfcAttributeDescriptionList)((RfcSearchRequest)Asn1Object.Get(1)).Get(7);
|
||||
var values = new string[attrs.Size()];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
values[i] = ((Asn1OctetString)attrs.Get(i)).StringValue();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string representation of the filter in this search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The string filter.
|
||||
/// </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);
|
||||
}
|
||||
/// <param name="ldapBase">The base distinguished name to search from.</param>
|
||||
/// <param name="scope">The scope of the entries to search. The following
|
||||
/// are the valid options:.
|
||||
/// <ul><li>SCOPE_BASE - searches only the base DN</li><li>SCOPE_ONE - searches only entries under the base DN</li><li>
|
||||
/// SCOPE_SUB - searches the base DN and all entries
|
||||
/// within its subtree
|
||||
/// </li></ul></param>
|
||||
/// <param name="filter">The search filter specifying the search criteria.</param>
|
||||
/// <param name="attrs">The names of attributes to retrieve.
|
||||
/// operation exceeds the time limit.</param>
|
||||
/// <param name="dereference">Specifies when aliases should be dereferenced.
|
||||
/// Must be one of the constants defined in
|
||||
/// LdapConstraints, which are DEREF_NEVER,
|
||||
/// DEREF_FINDING, DEREF_SEARCHING, or DEREF_ALWAYS.</param>
|
||||
/// <param name="maxResults">The maximum number of search results to return
|
||||
/// for a search request.
|
||||
/// The search operation will be terminated by the server
|
||||
/// with an LdapException.SIZE_LIMIT_EXCEEDED if the
|
||||
/// number of results exceed the maximum.</param>
|
||||
/// <param name="serverTimeLimit">The maximum time in seconds that the server
|
||||
/// should spend returning search results. This is a
|
||||
/// server-enforced limit. A value of 0 means
|
||||
/// no time limit.</param>
|
||||
/// <param name="typesOnly">If true, returns the names but not the values of
|
||||
/// the attributes found. If false, returns the
|
||||
/// names and values for attributes found.</param>
|
||||
/// <param name="cont">Any controls that apply to the search request.
|
||||
/// or null if none.</param>
|
||||
/// <seealso cref="LdapConnection.Search"></seealso>
|
||||
public LdapSearchRequest(
|
||||
String ldapBase,
|
||||
LdapScope scope,
|
||||
String filter,
|
||||
String[] attrs,
|
||||
Int32 dereference,
|
||||
Int32 maxResults,
|
||||
Int32 serverTimeLimit,
|
||||
Boolean typesOnly,
|
||||
LdapControl[] cont)
|
||||
: base(
|
||||
LdapOperation.SearchRequest,
|
||||
new RfcSearchRequest(ldapBase, scope, dereference, maxResults, serverTimeLimit, typesOnly, filter, attrs),
|
||||
cont) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an Iterator object representing the parsed filter for
|
||||
/// this search request.
|
||||
/// 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
|
||||
/// filter.
|
||||
/// Values returned as a byte array may represent UTF-8 characters or may
|
||||
/// be binary values. The possible Integer components of a search filter
|
||||
/// and the associated values that follow are:.
|
||||
/// <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>
|
||||
/// EQUALITY_MATCH - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// GREATER_OR_EQUAL - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// LESS_OR_EQUAL - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// APPROX_MATCH - followed by the attribute name represented as a
|
||||
/// 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>
|
||||
/// EXTENSIBLE_MATCH - followed by the name of the matching rule
|
||||
/// represented as a String, by the attribute name represented
|
||||
/// as a String, and by the attribute value represented as a
|
||||
/// byte array.
|
||||
/// </li><li>
|
||||
/// SUBSTRINGS - followed by the attribute name represented as a
|
||||
/// String, by one or more SUBSTRING components (INITIAL, ANY,
|
||||
/// or FINAL) followed by the SUBSTRING value.
|
||||
/// </li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The search filter.
|
||||
/// </value>
|
||||
public IEnumerator SearchFilter => this.RfcFilter.GetFilterIterator();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Base DN for a search request.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// the base DN for a search request.
|
||||
/// </returns>
|
||||
public String DN => this.Asn1Object.RequestDn;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the scope of a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The scope.
|
||||
/// </value>
|
||||
public Int32 Scope => ((Asn1Enumerated)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(1)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the behaviour of dereferencing aliases on a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dereference.
|
||||
/// </value>
|
||||
public Int32 Dereference => ((Asn1Enumerated)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(2)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the maximum number of entries to be returned on a search.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The maximum results.
|
||||
/// </value>
|
||||
public Int32 MaxResults => ((Asn1Integer)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(3)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the server time limit for a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The server time limit.
|
||||
/// </value>
|
||||
public Int32 ServerTimeLimit => ((Asn1Integer)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(4)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves whether attribute values or only attribute types(names) should
|
||||
/// be returned in a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [types only]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean TypesOnly => ((Asn1Boolean)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(5)).BooleanValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an array of attribute names to request for in a search.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The attributes.
|
||||
/// </value>
|
||||
public String[] Attributes {
|
||||
get {
|
||||
RfcAttributeDescriptionList attrs = (RfcAttributeDescriptionList)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(7);
|
||||
String[] values = new String[attrs.Size()];
|
||||
for(Int32 i = 0; i < values.Length; i++) {
|
||||
values[i] = ((Asn1OctetString)attrs.Get(i)).StringValue();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string representation of the filter in this search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The string filter.
|
||||
/// </value>
|
||||
public String StringFilter => this.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)this.Asn1Object.Get(1)).Get(6);
|
||||
}
|
||||
}
|
@ -1,95 +1,87 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using System;
|
||||
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>
|
||||
/// An LdapSearchResults object is returned from a synchronous search
|
||||
/// operation. It provides access to all results received during the
|
||||
/// operation (entries and exceptions).
|
||||
/// Initializes a new instance of the <see cref="LdapSearchResults" /> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapConnection.Search"></seealso>
|
||||
public sealed class LdapSearchResults
|
||||
{
|
||||
private readonly List<RfcLdapMessage> _messages;
|
||||
private readonly int _messageId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapSearchResults" /> class.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages.</param>
|
||||
/// <param name="messageId">The message identifier.</param>
|
||||
internal LdapSearchResults(List<RfcLdapMessage> messages, int messageId)
|
||||
{
|
||||
_messages = messages;
|
||||
_messageId = messageId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a count of the items in the search result.
|
||||
/// Returns a count of the entries and exceptions remaining in the object.
|
||||
/// If the search was submitted with a batch size greater than zero,
|
||||
/// getCount reports the number of results received so far but not enumerated
|
||||
/// with next(). If batch size equals zero, getCount reports the number of
|
||||
/// items received, since the application thread blocks until all results are
|
||||
/// received.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
public int Count => new List<RfcLdapMessage>(_messages)
|
||||
.Count(x => x.MessageId == _messageId && GetResponse(x) is LdapSearchResult);
|
||||
|
||||
/// <summary>
|
||||
/// Reports if there are more search results.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if there are more search results.
|
||||
/// </returns>
|
||||
public bool HasMore() => new List<RfcLdapMessage>(_messages)
|
||||
.Any(x => x.MessageId == _messageId && GetResponse(x) is LdapSearchResult);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next result as an LdapEntry.
|
||||
/// If automatic referral following is disabled or if a referral
|
||||
/// was not followed, next() will throw an LdapReferralException
|
||||
/// when the referral is received.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The next search result as an LdapEntry.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Next - No more results.</exception>
|
||||
public LdapEntry Next()
|
||||
{
|
||||
var list = new List<RfcLdapMessage>(_messages)
|
||||
.Where(x => x.MessageId == _messageId);
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
_messages.Remove(item);
|
||||
var response = GetResponse(item);
|
||||
|
||||
if (response is LdapSearchResult result)
|
||||
{
|
||||
return result.Entry;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="messages">The messages.</param>
|
||||
/// <param name="messageId">The message identifier.</param>
|
||||
internal LdapSearchResults(List<RfcLdapMessage> messages, Int32 messageId) {
|
||||
this._messages = messages;
|
||||
this._messageId = messageId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a count of the items in the search result.
|
||||
/// Returns a count of the entries and exceptions remaining in the object.
|
||||
/// If the search was submitted with a batch size greater than zero,
|
||||
/// getCount reports the number of results received so far but not enumerated
|
||||
/// with next(). If batch size equals zero, getCount reports the number of
|
||||
/// items received, since the application thread blocks until all results are
|
||||
/// received.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
public Int32 Count => new List<RfcLdapMessage>(this._messages)
|
||||
.Count(x => x.MessageId == this._messageId && GetResponse(x) is LdapSearchResult);
|
||||
|
||||
/// <summary>
|
||||
/// Reports if there are more search results.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if there are more search results.
|
||||
/// </returns>
|
||||
public Boolean HasMore() => new List<RfcLdapMessage>(this._messages)
|
||||
.Any(x => x.MessageId == this._messageId && GetResponse(x) is LdapSearchResult);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next result as an LdapEntry.
|
||||
/// If automatic referral following is disabled or if a referral
|
||||
/// was not followed, next() will throw an LdapReferralException
|
||||
/// when the referral is received.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The next search result as an LdapEntry.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Next - No more results.</exception>
|
||||
public LdapEntry Next() {
|
||||
IEnumerable<RfcLdapMessage> list = new List<RfcLdapMessage>(this._messages)
|
||||
.Where(x => x.MessageId == this._messageId);
|
||||
|
||||
foreach(RfcLdapMessage item in list) {
|
||||
_ = this._messages.Remove(item);
|
||||
LdapMessage response = GetResponse(item);
|
||||
|
||||
if(response is LdapSearchResult result) {
|
||||
return result.Entry;
|
||||
}
|
||||
}
|
||||
|
||||
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
@ -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>
|
||||
/// 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>
|
||||
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 bool _returnDelims;
|
||||
|
||||
private List<string> _elements;
|
||||
|
||||
private string _source;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="source">String to tokenize.</param>
|
||||
/// <param name="delimiters">String containing the delimiters.</param>
|
||||
/// <param name="retDel">if set to <c>true</c> [ret delete].</param>
|
||||
public Tokenizer(string source, string delimiters, bool retDel = false)
|
||||
{
|
||||
_elements = new List<string>();
|
||||
_delimiters = delimiters ?? _delimiters;
|
||||
_source = source;
|
||||
_returnDelims = retDel;
|
||||
if (_returnDelims)
|
||||
Tokenize();
|
||||
else
|
||||
_elements.AddRange(source.Split(_delimiters.ToCharArray()));
|
||||
RemoveEmptyStrings();
|
||||
}
|
||||
|
||||
public int Count => _elements.Count;
|
||||
|
||||
public bool HasMoreTokens() => _elements.Count > 0;
|
||||
|
||||
public string NextToken()
|
||||
{
|
||||
if (_source == string.Empty) throw new InvalidOperationException();
|
||||
|
||||
string result;
|
||||
if (_returnDelims)
|
||||
{
|
||||
RemoveEmptyStrings();
|
||||
result = _elements[0];
|
||||
_elements.RemoveAt(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
_elements = new List<string>();
|
||||
_elements.AddRange(_source.Split(_delimiters.ToCharArray()));
|
||||
RemoveEmptyStrings();
|
||||
result = _elements[0];
|
||||
_elements.RemoveAt(0);
|
||||
_source = _source.Remove(_source.IndexOf(result, StringComparison.Ordinal), result.Length);
|
||||
_source = _source.TrimStart(_delimiters.ToCharArray());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void RemoveEmptyStrings()
|
||||
{
|
||||
for (var index = 0; index < _elements.Count; index++)
|
||||
{
|
||||
if (_elements[index] != string.Empty) continue;
|
||||
|
||||
_elements.RemoveAt(index);
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
private void Tokenize()
|
||||
{
|
||||
var tempstr = _source;
|
||||
if (tempstr.IndexOfAny(_delimiters.ToCharArray()) < 0 && tempstr.Length > 0)
|
||||
{
|
||||
_elements.Add(tempstr);
|
||||
}
|
||||
else if (tempstr.IndexOfAny(_delimiters.ToCharArray()) < 0 && tempstr.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (tempstr.IndexOfAny(_delimiters.ToCharArray()) >= 0)
|
||||
{
|
||||
if (tempstr.IndexOfAny(_delimiters.ToCharArray()) == 0)
|
||||
{
|
||||
if (tempstr.Length > 1)
|
||||
{
|
||||
_elements.Add(tempstr.Substring(0, 1));
|
||||
tempstr = tempstr.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempstr = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var toks = tempstr.Substring(0, tempstr.IndexOfAny(_delimiters.ToCharArray()));
|
||||
_elements.Add(toks);
|
||||
_elements.Add(tempstr.Substring(toks.Length, 1));
|
||||
|
||||
tempstr = tempstr.Length > toks.Length + 1 ? tempstr.Substring(toks.Length + 1) : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if (tempstr.Length > 0)
|
||||
{
|
||||
_elements.Add(tempstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="source">String to tokenize.</param>
|
||||
/// <param name="delimiters">String containing the delimiters.</param>
|
||||
/// <param name="retDel">if set to <c>true</c> [ret delete].</param>
|
||||
public Tokenizer(String source, String delimiters, Boolean retDel = false) {
|
||||
this._elements = new List<String>();
|
||||
this._delimiters = delimiters ?? this._delimiters;
|
||||
this._source = source;
|
||||
this._returnDelims = retDel;
|
||||
if(this._returnDelims) {
|
||||
this.Tokenize();
|
||||
} else {
|
||||
this._elements.AddRange(source.Split(this._delimiters.ToCharArray()));
|
||||
}
|
||||
|
||||
this.RemoveEmptyStrings();
|
||||
}
|
||||
|
||||
public Int32 Count => this._elements.Count;
|
||||
|
||||
public Boolean HasMoreTokens() => this._elements.Count > 0;
|
||||
|
||||
public String NextToken() {
|
||||
if(this._source == String.Empty) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
String result;
|
||||
if(this._returnDelims) {
|
||||
this.RemoveEmptyStrings();
|
||||
result = this._elements[0];
|
||||
this._elements.RemoveAt(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
this._elements = new List<String>();
|
||||
this._elements.AddRange(this._source.Split(this._delimiters.ToCharArray()));
|
||||
this.RemoveEmptyStrings();
|
||||
result = this._elements[0];
|
||||
this._elements.RemoveAt(0);
|
||||
this._source = this._source.Remove(this._source.IndexOf(result, StringComparison.Ordinal), result.Length);
|
||||
this._source = this._source.TrimStart(this._delimiters.ToCharArray());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void RemoveEmptyStrings() {
|
||||
for(Int32 index = 0; index < this._elements.Count; index++) {
|
||||
if(this._elements[index] != String.Empty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._elements.RemoveAt(index);
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
private void Tokenize() {
|
||||
String tempstr = this._source;
|
||||
if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) < 0 && tempstr.Length > 0) {
|
||||
this._elements.Add(tempstr);
|
||||
} else if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) < 0 && tempstr.Length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while(tempstr.IndexOfAny(this._delimiters.ToCharArray()) >= 0) {
|
||||
if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) == 0) {
|
||||
if(tempstr.Length > 1) {
|
||||
this._elements.Add(tempstr.Substring(0, 1));
|
||||
tempstr = tempstr.Substring(1);
|
||||
} else {
|
||||
tempstr = String.Empty;
|
||||
}
|
||||
} else {
|
||||
String toks = tempstr.Substring(0, tempstr.IndexOfAny(this._delimiters.ToCharArray()));
|
||||
this._elements.Add(toks);
|
||||
this._elements.Add(tempstr.Substring(toks.Length, 1));
|
||||
|
||||
tempstr = tempstr.Length > toks.Length + 1 ? tempstr.Substring(toks.Length + 1) : String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if(tempstr.Length > 0) {
|
||||
this._elements.Add(tempstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Matching Rule Assertion.
|
||||
/// <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) {
|
||||
this.Add(new Asn1Tagged(new Asn1Identifier(1), new Asn1OctetString(matchingRule), false));
|
||||
}
|
||||
|
||||
if(type != null) {
|
||||
this.Add(new Asn1Tagged(new Asn1Identifier(2), new Asn1OctetString(type), false));
|
||||
}
|
||||
|
||||
this.Add(new Asn1Tagged(new Asn1Identifier(3), new Asn1OctetString(matchValue), false));
|
||||
|
||||
// if dnAttributes if false, that is the default value and we must not
|
||||
// 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>
|
||||
/// Represents an Ldap Matching Rule Assertion.
|
||||
/// <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>
|
||||
/// Initializes a new instance of the <see cref="BindProperties" /> class.
|
||||
/// </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,
|
||||
int derefAliases,
|
||||
int sizeLimit,
|
||||
int timeLimit,
|
||||
bool typesOnly,
|
||||
string filter,
|
||||
string[] attributes)
|
||||
: base(8)
|
||||
{
|
||||
Add(basePath);
|
||||
Add(new Asn1Enumerated(scope));
|
||||
Add(new Asn1Enumerated(derefAliases));
|
||||
Add(new Asn1Integer(sizeLimit));
|
||||
Add(new Asn1Integer(timeLimit));
|
||||
Add(new Asn1Boolean(typesOnly));
|
||||
Add(new RfcFilter(filter));
|
||||
Add(new RfcAttributeDescriptionList(attributes));
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchRequest);
|
||||
|
||||
public string GetRequestDN() => ((Asn1OctetString) 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)
|
||||
{
|
||||
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; }
|
||||
}
|
||||
/// <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(
|
||||
Int32 version,
|
||||
String dn,
|
||||
String method,
|
||||
Boolean anonymous) {
|
||||
this.ProtocolVersion = version;
|
||||
this.AuthenticationDN = dn;
|
||||
this.AuthenticationMethod = method;
|
||||
this.Anonymous = anonymous;
|
||||
}
|
||||
|
||||
public Int32 ProtocolVersion {
|
||||
get;
|
||||
}
|
||||
|
||||
public String AuthenticationDN {
|
||||
get;
|
||||
}
|
||||
|
||||
public String AuthenticationMethod {
|
||||
get;
|
||||
}
|
||||
|
||||
public Boolean Anonymous {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// Represents an Ldap Control.
|
||||
/// <pre>
|
||||
/// Control ::= SEQUENCE {
|
||||
/// controlType LdapOID,
|
||||
/// criticality BOOLEAN DEFAULT FALSE,
|
||||
/// controlValue OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// Initializes a new instance of the <see cref="RfcControl"/> class.
|
||||
/// Note: criticality is only added if true, as per RFC 2251 sec 5.1 part
|
||||
/// (4): If a value of a type is its default value, it MUST be
|
||||
/// absent.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcControl : Asn1Sequence
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcControl"/> class.
|
||||
/// Note: criticality is only added if true, as per RFC 2251 sec 5.1 part
|
||||
/// (4): If a value of a type is its default value, it MUST be
|
||||
/// absent.
|
||||
/// </summary>
|
||||
/// <param name="controlType">Type of the control.</param>
|
||||
/// <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)
|
||||
{
|
||||
Add(controlType);
|
||||
Add(criticality ?? new Asn1Boolean(false));
|
||||
|
||||
if (controlValue != null)
|
||||
Add(controlValue);
|
||||
}
|
||||
|
||||
public RfcControl(Asn1Structured seqObj)
|
||||
: base(3)
|
||||
{
|
||||
for (var i = 0; i < seqObj.Size(); i++)
|
||||
Add(seqObj.Get(i));
|
||||
}
|
||||
|
||||
public Asn1OctetString ControlType => (Asn1OctetString)Get(0);
|
||||
|
||||
public Asn1Boolean Criticality => Size() > 1 && Get(1) is Asn1Boolean boolean ? boolean : new Asn1Boolean(false);
|
||||
|
||||
public Asn1OctetString ControlValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Size() > 2)
|
||||
{
|
||||
// MUST be a control value
|
||||
return (Asn1OctetString)Get(2);
|
||||
}
|
||||
|
||||
return Size() > 1 && Get(1) is Asn1OctetString s ? s : null;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
if (Size() == 3)
|
||||
{
|
||||
// We already have a control value, replace it
|
||||
Set(2, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Size() == 2)
|
||||
{
|
||||
// Get the second element
|
||||
var obj = Get(1);
|
||||
|
||||
// Is this a control value
|
||||
if (obj is Asn1OctetString)
|
||||
{
|
||||
// replace this one
|
||||
Set(1, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a new one at the end
|
||||
Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents Ldap Sasl Credentials.
|
||||
/// <pre>
|
||||
/// SaslCredentials ::= SEQUENCE {
|
||||
/// mechanism LdapString,
|
||||
/// credentials OCTET STRING OPTIONAL }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcSaslCredentials : Asn1Sequence
|
||||
{
|
||||
public RfcSaslCredentials(string mechanism, sbyte[] credentials = null)
|
||||
: base(2)
|
||||
{
|
||||
Add(mechanism);
|
||||
if (credentials != null)
|
||||
Add(new Asn1OctetString(credentials));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
/// <param name="controlType">Type of the control.</param>
|
||||
/// <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) {
|
||||
this.Add(controlType);
|
||||
this.Add(criticality ?? new Asn1Boolean(false));
|
||||
|
||||
if(controlValue != null) {
|
||||
this.Add(controlValue);
|
||||
}
|
||||
}
|
||||
|
||||
public RfcControl(Asn1Structured seqObj)
|
||||
: base(3) {
|
||||
for(Int32 i = 0; i < seqObj.Size(); i++) {
|
||||
this.Add(seqObj.Get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1OctetString ControlType => (Asn1OctetString)this.Get(0);
|
||||
|
||||
public Asn1Boolean Criticality => this.Size() > 1 && this.Get(1) is Asn1Boolean boolean ? boolean : new Asn1Boolean(false);
|
||||
|
||||
public Asn1OctetString ControlValue {
|
||||
get {
|
||||
if(this.Size() > 2) {
|
||||
// MUST be a control value
|
||||
return (Asn1OctetString)this.Get(2);
|
||||
}
|
||||
|
||||
return this.Size() > 1 && this.Get(1) is Asn1OctetString s ? s : null;
|
||||
}
|
||||
set {
|
||||
if(value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.Size() == 3) {
|
||||
// We already have a control value, replace it
|
||||
this.Set(2, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.Size() == 2) {
|
||||
// Get the second element
|
||||
Asn1Object obj = this.Get(1);
|
||||
|
||||
// Is this a control value
|
||||
if(obj is Asn1OctetString) {
|
||||
// replace this one
|
||||
this.Set(1, value);
|
||||
} else {
|
||||
// add a new one at the end
|
||||
this.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents Ldap Sasl Credentials.
|
||||
/// <pre>
|
||||
/// SaslCredentials ::= SEQUENCE {
|
||||
/// mechanism LdapString,
|
||||
/// credentials OCTET STRING OPTIONAL }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcSaslCredentials : Asn1Sequence {
|
||||
public RfcSaslCredentials(String mechanism, SByte[] credentials = null)
|
||||
: base(2) {
|
||||
this.Add(mechanism);
|
||||
if(credentials != null) {
|
||||
this.Add(new Asn1OctetString(credentials));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
@ -1,246 +1,238 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
using System;
|
||||
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>
|
||||
/// Encapsulates a single search result that is in response to an asynchronous
|
||||
/// search operation.
|
||||
/// Initializes a new instance of the <see cref="RfcSearchResultReference"/> class.
|
||||
/// The only time a client will create a SearchResultReference is when it is
|
||||
/// decoding it from an Stream.
|
||||
/// </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 (_entry != null) return _entry;
|
||||
|
||||
var attrs = new LdapAttributeSet();
|
||||
var entry = (RfcSearchResultEntry) Message.Response;
|
||||
|
||||
foreach (var o in entry.Attributes.ToArray())
|
||||
{
|
||||
var seq = (Asn1Sequence) o;
|
||||
var attr = new LdapAttribute(((Asn1OctetString)seq.Get(0)).StringValue());
|
||||
var set = (Asn1Set)seq.Get(1);
|
||||
|
||||
foreach (var t in set.ToArray())
|
||||
{
|
||||
attr.AddValue(((Asn1OctetString)t).ByteValue());
|
||||
}
|
||||
|
||||
attrs.Add(attr);
|
||||
}
|
||||
|
||||
_entry = new LdapEntry(entry.ObjectName, attrs);
|
||||
|
||||
return _entry;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => _entry?.ToString() ?? base.ToString();
|
||||
}
|
||||
|
||||
/// <param name="stream">The streab.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcSearchResultReference(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResultReference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Extended Response.
|
||||
/// <pre>
|
||||
/// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
|
||||
/// COMPONENTS OF LdapResult,
|
||||
/// responseName [10] LdapOID OPTIONAL,
|
||||
/// response [11] OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcExtendedResponse : Asn1Sequence, IRfcResponse {
|
||||
public const Int32 ResponseNameCode = 10;
|
||||
|
||||
public const Int32 ResponseCode = 11;
|
||||
|
||||
private readonly Int32 _referralIndex;
|
||||
private readonly Int32 _responseNameIndex;
|
||||
private readonly Int32 _responseIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Reference.
|
||||
/// <pre>
|
||||
/// SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LdapURL
|
||||
/// </pre>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
|
||||
internal class RfcSearchResultReference : Asn1SequenceOf
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcSearchResultReference"/> class.
|
||||
/// The only time a client will create a SearchResultReference is when it is
|
||||
/// decoding it from an Stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The streab.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcSearchResultReference(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResultReference);
|
||||
}
|
||||
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcExtendedResponse(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
if(this.Size() <= 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(Int32 i = 3; i < this.Size(); i++) {
|
||||
Asn1Tagged obj = (Asn1Tagged)this.Get(i);
|
||||
Asn1Identifier id = obj.GetIdentifier();
|
||||
|
||||
switch(id.Tag) {
|
||||
case RfcLdapResult.Referral:
|
||||
SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
|
||||
|
||||
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>
|
||||
/// Represents an Ldap Extended Response.
|
||||
/// <pre>
|
||||
/// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
|
||||
/// COMPONENTS OF LdapResult,
|
||||
/// responseName [10] LdapOID OPTIONAL,
|
||||
/// response [11] OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// 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>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcExtendedResponse : Asn1Sequence, IRfcResponse
|
||||
{
|
||||
public const int ResponseNameCode = 10;
|
||||
|
||||
public const int ResponseCode = 11;
|
||||
|
||||
private readonly int _referralIndex;
|
||||
private readonly int _responseNameIndex;
|
||||
private readonly int _responseIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcExtendedResponse(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
if (Size() <= 3) return;
|
||||
|
||||
for (var i = 3; i < Size(); i++)
|
||||
{
|
||||
var obj = (Asn1Tagged) Get(i);
|
||||
var id = obj.GetIdentifier();
|
||||
|
||||
switch (id.Tag)
|
||||
{
|
||||
case RfcLdapResult.Referral:
|
||||
var content = ((Asn1OctetString) obj.TaggedValue).ByteValue();
|
||||
|
||||
using (var bais = new MemoryStream(content.ToByteArray()))
|
||||
Set(i, new Asn1SequenceOf(bais, content.Length));
|
||||
|
||||
_referralIndex = i;
|
||||
break;
|
||||
case ResponseNameCode:
|
||||
Set(i, new Asn1OctetString(((Asn1OctetString) obj.TaggedValue).ByteValue()));
|
||||
_responseNameIndex = i;
|
||||
break;
|
||||
case ResponseCode:
|
||||
Set(i, obj.TaggedValue);
|
||||
_responseIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1OctetString ResponseName => _responseNameIndex != 0 ? (Asn1OctetString) Get(_responseNameIndex) : null;
|
||||
|
||||
public Asn1OctetString Response => _responseIndex != 0 ? (Asn1OctetString) Get(_responseIndex) : null;
|
||||
|
||||
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()
|
||||
=> _referralIndex != 0 ? (Asn1SequenceOf) Get(_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>
|
||||
/// 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);
|
||||
}
|
||||
/// <param name="stream">The in renamed.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcBindResponse(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);
|
||||
|
||||
if(obj.GetIdentifier().Tag != RfcLdapResult.Referral) {
|
||||
return;
|
||||
}
|
||||
|
||||
SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
|
||||
|
||||
using(MemoryStream bais = new MemoryStream(content.ToByteArray())) {
|
||||
this.Set(3, new Asn1SequenceOf(bais, content.Length));
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0);
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue());
|
||||
|
||||
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue());
|
||||
|
||||
public Asn1SequenceOf GetReferral() => this.Size() > 3 && this.Get(3) is Asn1SequenceOf ? (Asn1SequenceOf)this.Get(3) : null;
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.BindResponse);
|
||||
}
|
||||
|
||||
/// <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 Int32 TagResponseName = 0;
|
||||
public const Int32 TagResponse = 1;
|
||||
|
||||
public RfcIntermediateResponse(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
Int32 i = this.Size() >= 3 ? 3 : 0;
|
||||
|
||||
for(; i < this.Size(); i++) {
|
||||
Asn1Tagged obj = (Asn1Tagged)this.Get(i);
|
||||
|
||||
switch(obj.GetIdentifier().Tag) {
|
||||
case TagResponseName:
|
||||
this.Set(i, new Asn1OctetString(((Asn1OctetString)obj.TaggedValue).ByteValue()));
|
||||
break;
|
||||
case TagResponse:
|
||||
this.Set(i, obj.TaggedValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1Enumerated GetResultCode() => this.Size() > 3 ? (Asn1Enumerated)this.Get(0) : null;
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => this.Size() > 3 ? new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue()) : null;
|
||||
|
||||
public Asn1OctetString GetErrorMessage() =>
|
||||
this.Size() > 3 ? new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue()) : null;
|
||||
|
||||
public Asn1SequenceOf GetReferral() => this.Size() > 3 ? (Asn1SequenceOf)this.Get(3) : null;
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.IntermediateResponse);
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// 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.).
|
||||
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
|
||||
/// Create an RfcLdapMessage request from input parameters.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal sealed class RfcLdapMessage : Asn1Sequence
|
||||
{
|
||||
private readonly Asn1Object _op;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
|
||||
/// Create an RfcLdapMessage request from input parameters.
|
||||
/// </summary>
|
||||
/// <param name="op">The op.</param>
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="controls">The controls.</param>
|
||||
public RfcLdapMessage(IRfcRequest op, RfcControls controls)
|
||||
: base(3) {
|
||||
this._op = (Asn1Object)op;
|
||||
|
||||
this.Add(new RfcMessageID()); // MessageID has static counter
|
||||
this.Add((Asn1Object)op);
|
||||
if(controls != null) {
|
||||
this.Add(controls);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents Ldap Controls.
|
||||
/// <pre>
|
||||
/// Controls ::= SEQUENCE OF Control
|
||||
/// </pre>
|
||||
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
|
||||
/// Will decode an RfcLdapMessage directly from an InputStream.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
|
||||
internal class RfcControls : Asn1SequenceOf
|
||||
{
|
||||
public const int Controls = 0;
|
||||
|
||||
public RfcControls()
|
||||
: base(5)
|
||||
{
|
||||
}
|
||||
|
||||
public RfcControls(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
// Convert each SEQUENCE element to a Control
|
||||
for (var i = 0; i < Size(); i++)
|
||||
{
|
||||
var tempControl = new RfcControl((Asn1Sequence) Get(i));
|
||||
Set(i, tempControl);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(RfcControl control) => base.Add(control);
|
||||
|
||||
public void Set(int index, RfcControl control) => base.Set(index, control);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(Controls, true);
|
||||
}
|
||||
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
/// <exception cref="Exception">RfcLdapMessage: Invalid tag: " + protocolOpId.Tag.</exception>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
|
||||
public RfcLdapMessage(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
// Decode implicitly tagged protocol operation from an Asn1Tagged type
|
||||
// to its appropriate application type.
|
||||
Asn1Tagged protocolOp = (Asn1Tagged)this.Get(1);
|
||||
Asn1Identifier protocolOpId = protocolOp.GetIdentifier();
|
||||
SByte[] content = ((Asn1OctetString)protocolOp.TaggedValue).ByteValue();
|
||||
MemoryStream bais = new MemoryStream(content.ToByteArray());
|
||||
|
||||
switch((LdapOperation)protocolOpId.Tag) {
|
||||
case LdapOperation.SearchResponse:
|
||||
this.Set(1, new RfcSearchResultEntry(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.SearchResult:
|
||||
this.Set(1, new RfcSearchResultDone(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.SearchResultReference:
|
||||
this.Set(1, new RfcSearchResultReference(bais, content.Length));
|
||||
break;
|
||||
|
||||
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>
|
||||
/// 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.
|
||||
/// Gets the result code.
|
||||
/// </summary>
|
||||
internal interface IRfcResponse
|
||||
{
|
||||
/// <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();
|
||||
}
|
||||
|
||||
/// <returns>Asn1Enumerated.</returns>
|
||||
Asn1Enumerated GetResultCode();
|
||||
|
||||
/// <summary>
|
||||
/// This interface represents Protocol Operations that are requests from a
|
||||
/// client.
|
||||
/// Gets the matched dn.
|
||||
/// </summary>
|
||||
internal interface IRfcRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a new request using the data from the this object.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="System.String" />.</returns>
|
||||
string GetRequestDN();
|
||||
}
|
||||
|
||||
/// <returns>RfcLdapDN.</returns>
|
||||
Asn1OctetString GetMatchedDN();
|
||||
|
||||
/// <summary>
|
||||
/// Represents an LdapResult.
|
||||
/// <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>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <returns>RfcLdapString.</returns>
|
||||
Asn1OctetString GetErrorMessage();
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Done Response.
|
||||
/// <pre>
|
||||
/// SearchResultDone ::= [APPLICATION 5] LdapResult
|
||||
/// </pre>
|
||||
/// Gets the referral.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.RfcLdapResult" />
|
||||
internal class RfcSearchResultDone : RfcLdapResult
|
||||
{
|
||||
public RfcSearchResultDone(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResult);
|
||||
}
|
||||
|
||||
/// <returns>Asn1SequenceOf.</returns>
|
||||
Asn1SequenceOf GetReferral();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface represents Protocol Operations that are requests from a
|
||||
/// client.
|
||||
/// </summary>
|
||||
internal interface IRfcRequest {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Entry.
|
||||
/// <pre>
|
||||
/// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
|
||||
/// objectName LdapDN,
|
||||
/// attributes PartialAttributeList }
|
||||
/// </pre>
|
||||
/// Builds a new request using the data from the this object.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal sealed class RfcSearchResultEntry : Asn1Sequence
|
||||
{
|
||||
public RfcSearchResultEntry(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
}
|
||||
|
||||
public string ObjectName => ((Asn1OctetString) Get(0)).StringValue();
|
||||
|
||||
public Asn1Sequence Attributes => (Asn1Sequence) Get(1);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResponse);
|
||||
}
|
||||
|
||||
/// <returns>A <see cref="System.String" />.</returns>
|
||||
String GetRequestDN();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an LdapResult.
|
||||
/// <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>
|
||||
/// <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>
|
||||
/// 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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// 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 Int32 MessageId {
|
||||
get {
|
||||
lock(SyncRoot) {
|
||||
return _messageId < Int32.MaxValue ? ++_messageId : (_messageId = 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +1,42 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Modify Request.
|
||||
/// <pre>
|
||||
/// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
|
||||
/// object LdapDN,
|
||||
/// modification SEQUENCE OF SEQUENCE {
|
||||
/// operation ENUMERATED {
|
||||
/// add (0),
|
||||
/// delete (1),
|
||||
/// replace (2) },
|
||||
/// modification AttributeTypeAndValues } }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
|
||||
internal sealed class RfcModifyRequest
|
||||
: Asn1Sequence, IRfcRequest
|
||||
{
|
||||
public RfcModifyRequest(string obj, Asn1SequenceOf modification)
|
||||
: base(2)
|
||||
{
|
||||
Add(obj);
|
||||
Add(modification);
|
||||
}
|
||||
|
||||
public Asn1SequenceOf Modifications => (Asn1SequenceOf)Get(1);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyRequest);
|
||||
|
||||
public string GetRequestDN() => ((Asn1OctetString)Get(0)).StringValue();
|
||||
}
|
||||
|
||||
internal class RfcModifyResponse : RfcLdapResult
|
||||
{
|
||||
public RfcModifyResponse(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyResponse);
|
||||
}
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Modify Request.
|
||||
/// <pre>
|
||||
/// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
|
||||
/// object LdapDN,
|
||||
/// modification SEQUENCE OF SEQUENCE {
|
||||
/// operation ENUMERATED {
|
||||
/// add (0),
|
||||
/// delete (1),
|
||||
/// replace (2) },
|
||||
/// modification AttributeTypeAndValues } }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
|
||||
internal sealed class RfcModifyRequest
|
||||
: Asn1Sequence, IRfcRequest {
|
||||
public RfcModifyRequest(String obj, Asn1SequenceOf modification)
|
||||
: base(2) {
|
||||
this.Add(obj);
|
||||
this.Add(modification);
|
||||
}
|
||||
|
||||
public Asn1SequenceOf Modifications => (Asn1SequenceOf)this.Get(1);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyRequest);
|
||||
|
||||
public String GetRequestDN() => ((Asn1OctetString)this.Get(0)).StringValue();
|
||||
}
|
||||
|
||||
internal class RfcModifyResponse : RfcLdapResult {
|
||||
public RfcModifyResponse(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyResponse);
|
||||
}
|
||||
}
|
@ -1,397 +1,390 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Net.Security;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Net.Security;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
#if !NETSTANDARD1_3
|
||||
using System.Net.Mail;
|
||||
using System.Net.Mail;
|
||||
#else
|
||||
using Exceptions;
|
||||
using Exceptions;
|
||||
#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>
|
||||
/// 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>
|
||||
/// <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>
|
||||
/// 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; }
|
||||
|
||||
/// <param name="host">The host.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <exception cref="ArgumentNullException">host.</exception>
|
||||
public SmtpClient(String host, Int32 port) {
|
||||
this.Host = host ?? throw new ArgumentNullException(nameof(host));
|
||||
this.Port = port;
|
||||
this.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 Int32 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 Boolean 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
|
||||
/// <summary>
|
||||
/// Sends an email message asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <param name="callback">The callback.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous of send email operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">message.</exception>
|
||||
public Task SendMailAsync(
|
||||
MailMessage message,
|
||||
string sessionId = null,
|
||||
CancellationToken ct = default,
|
||||
RemoteCertificateValidationCallback callback = null)
|
||||
{
|
||||
if (message == null)
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
|
||||
var state = new SmtpSessionState
|
||||
{
|
||||
AuthMode = Credentials == null ? string.Empty : SmtpDefinitions.SmtpAuthMethods.Login,
|
||||
ClientHostname = ClientHostname,
|
||||
IsChannelSecure = EnableSsl,
|
||||
SenderAddress = message.From.Address,
|
||||
};
|
||||
|
||||
if (Credentials != null)
|
||||
{
|
||||
state.Username = Credentials.UserName;
|
||||
state.Password = Credentials.Password;
|
||||
}
|
||||
|
||||
foreach (var recipient in message.To)
|
||||
{
|
||||
state.Recipients.Add(recipient.Address);
|
||||
}
|
||||
|
||||
state.DataBuffer.AddRange(message.ToMimeMessage().ToArray());
|
||||
|
||||
return SendMailAsync(state, sessionId, ct, callback);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sends an email message asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <param name="callback">The callback.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous of send email operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">message.</exception>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
|
||||
public Task SendMailAsync(
|
||||
MailMessage message,
|
||||
String sessionId = null,
|
||||
CancellationToken ct = default,
|
||||
RemoteCertificateValidationCallback callback = null) {
|
||||
if(message == null) {
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
SmtpSessionState state = new SmtpSessionState {
|
||||
AuthMode = this.Credentials == null ? String.Empty : SmtpDefinitions.SmtpAuthMethods.Login,
|
||||
ClientHostname = ClientHostname,
|
||||
IsChannelSecure = EnableSsl,
|
||||
SenderAddress = message.From.Address,
|
||||
};
|
||||
|
||||
if(this.Credentials != null) {
|
||||
state.Username = this.Credentials.UserName;
|
||||
state.Password = this.Credentials.Password;
|
||||
}
|
||||
|
||||
foreach(MailAddress recipient in message.To) {
|
||||
state.Recipients.Add(recipient.Address);
|
||||
}
|
||||
|
||||
state.DataBuffer.AddRange(message.ToMimeMessage().ToArray());
|
||||
|
||||
return this.SendMailAsync(state, sessionId, ct, callback);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Sends an email message using a session state object.
|
||||
/// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but
|
||||
/// rather from the properties of this class.
|
||||
/// </summary>
|
||||
/// <param name="sessionState">The state.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <param name="callback">The callback.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous of send email operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">sessionState.</exception>
|
||||
public Task SendMailAsync(
|
||||
SmtpSessionState sessionState,
|
||||
string sessionId = null,
|
||||
CancellationToken ct = default,
|
||||
RemoteCertificateValidationCallback callback = null)
|
||||
{
|
||||
if (sessionState == null)
|
||||
throw new ArgumentNullException(nameof(sessionState));
|
||||
|
||||
return SendMailAsync(new[] { sessionState }, sessionId, ct, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// rather from the properties of this class.
|
||||
/// </summary>
|
||||
/// <param name="sessionStates">The session states.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <param name="callback">The callback.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous of send email operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">sessionStates.</exception>
|
||||
/// <exception cref="SecurityException">Could not upgrade the channel to SSL.</exception>
|
||||
/// <exception cref="SmtpException">Defines an SMTP Exceptions class.</exception>
|
||||
public async Task SendMailAsync(
|
||||
IEnumerable<SmtpSessionState> sessionStates,
|
||||
string sessionId = null,
|
||||
CancellationToken ct = default,
|
||||
RemoteCertificateValidationCallback callback = null)
|
||||
{
|
||||
if (sessionStates == null)
|
||||
throw new ArgumentNullException(nameof(sessionStates));
|
||||
|
||||
using (var tcpClient = new TcpClient())
|
||||
{
|
||||
await tcpClient.ConnectAsync(Host, Port).ConfigureAwait(false);
|
||||
|
||||
using (var connection = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 1000))
|
||||
{
|
||||
var sender = new SmtpSender(sessionId);
|
||||
|
||||
try
|
||||
{
|
||||
// Read the greeting message
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
|
||||
// EHLO 1
|
||||
await SendEhlo(ct, sender, connection).ConfigureAwait(false);
|
||||
|
||||
// STARTTLS
|
||||
if (EnableSsl)
|
||||
{
|
||||
sender.RequestText = $"{SmtpCommandNames.STARTTLS}";
|
||||
|
||||
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
sender.ValidateReply();
|
||||
|
||||
if (await connection.UpgradeToSecureAsClientAsync(callback: callback).ConfigureAwait(false) == false)
|
||||
throw new SecurityException("Could not upgrade the channel to SSL.");
|
||||
}
|
||||
|
||||
// EHLO 2
|
||||
await SendEhlo(ct, sender, connection).ConfigureAwait(false);
|
||||
|
||||
// AUTH
|
||||
if (Credentials != null)
|
||||
{
|
||||
var auth = new ConnectionAuth(connection, sender, Credentials);
|
||||
await auth.AuthenticateAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var sessionState in sessionStates)
|
||||
{
|
||||
{
|
||||
// MAIL FROM
|
||||
sender.RequestText = $"{SmtpCommandNames.MAIL} FROM:<{sessionState.SenderAddress}>";
|
||||
|
||||
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
sender.ValidateReply();
|
||||
}
|
||||
|
||||
// RCPT TO
|
||||
foreach (var recipient in sessionState.Recipients)
|
||||
{
|
||||
sender.RequestText = $"{SmtpCommandNames.RCPT} TO:<{recipient}>";
|
||||
|
||||
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);
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
sender.ValidateReply();
|
||||
}
|
||||
|
||||
{
|
||||
// CONTENT
|
||||
var dataTerminator = sessionState.DataBuffer
|
||||
.Skip(sessionState.DataBuffer.Count - 5)
|
||||
.ToText();
|
||||
|
||||
sender.RequestText = $"Buffer ({sessionState.DataBuffer.Count} bytes)";
|
||||
|
||||
await connection.WriteDataAsync(sessionState.DataBuffer.ToArray(), true, ct).ConfigureAwait(false);
|
||||
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);
|
||||
sender.ValidateReply();
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
throw new SmtpException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendEhlo(CancellationToken ct, SmtpSender sender, Connection connection)
|
||||
{
|
||||
sender.RequestText = $"{SmtpCommandNames.EHLO} {ClientHostname}";
|
||||
|
||||
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
|
||||
|
||||
do
|
||||
{
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
} while (!sender.IsReplyOk);
|
||||
|
||||
sender.ValidateReply();
|
||||
}
|
||||
|
||||
private class ConnectionAuth
|
||||
{
|
||||
private readonly SmtpSender _sender;
|
||||
private readonly Connection _connection;
|
||||
private readonly NetworkCredential _credentials;
|
||||
|
||||
public ConnectionAuth(Connection connection, SmtpSender sender, NetworkCredential credentials)
|
||||
{
|
||||
_connection = connection;
|
||||
_sender = sender;
|
||||
_credentials = credentials;
|
||||
}
|
||||
|
||||
public async Task AuthenticateAsync(CancellationToken ct)
|
||||
{
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an email message using a session state object.
|
||||
/// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but
|
||||
/// rather from the properties of this class.
|
||||
/// </summary>
|
||||
/// <param name="sessionState">The state.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <param name="callback">The callback.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous of send email operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">sessionState.</exception>
|
||||
public Task SendMailAsync(
|
||||
SmtpSessionState sessionState,
|
||||
String sessionId = null,
|
||||
CancellationToken ct = default,
|
||||
RemoteCertificateValidationCallback callback = null) {
|
||||
if(sessionState == null) {
|
||||
throw new ArgumentNullException(nameof(sessionState));
|
||||
}
|
||||
|
||||
return this.SendMailAsync(new[] { sessionState }, sessionId, ct, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// rather from the properties of this class.
|
||||
/// </summary>
|
||||
/// <param name="sessionStates">The session states.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <param name="callback">The callback.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous of send email operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">sessionStates.</exception>
|
||||
/// <exception cref="SecurityException">Could not upgrade the channel to SSL.</exception>
|
||||
/// <exception cref="SmtpException">Defines an SMTP Exceptions class.</exception>
|
||||
public async Task SendMailAsync(
|
||||
IEnumerable<SmtpSessionState> sessionStates,
|
||||
String sessionId = null,
|
||||
CancellationToken ct = default,
|
||||
RemoteCertificateValidationCallback callback = null) {
|
||||
if(sessionStates == null) {
|
||||
throw new ArgumentNullException(nameof(sessionStates));
|
||||
}
|
||||
|
||||
using(TcpClient tcpClient = new TcpClient()) {
|
||||
await tcpClient.ConnectAsync(this.Host, this.Port).ConfigureAwait(false);
|
||||
|
||||
using(Connection connection = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 1000)) {
|
||||
SmtpSender sender = new SmtpSender(sessionId);
|
||||
|
||||
try {
|
||||
// Read the greeting message
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
|
||||
// EHLO 1
|
||||
await this.SendEhlo(ct, sender, connection).ConfigureAwait(false);
|
||||
|
||||
// STARTTLS
|
||||
if(this.EnableSsl) {
|
||||
sender.RequestText = $"{SmtpCommandNames.STARTTLS}";
|
||||
|
||||
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
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);
|
||||
|
||||
// AUTH
|
||||
if(this.Credentials != null) {
|
||||
ConnectionAuth auth = new ConnectionAuth(connection, sender, this.Credentials);
|
||||
await auth.AuthenticateAsync(ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach(SmtpSessionState sessionState in sessionStates) {
|
||||
{
|
||||
// MAIL FROM
|
||||
sender.RequestText = $"{SmtpCommandNames.MAIL} FROM:<{sessionState.SenderAddress}>";
|
||||
|
||||
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
sender.ValidateReply();
|
||||
}
|
||||
|
||||
// RCPT TO
|
||||
foreach(String recipient in sessionState.Recipients) {
|
||||
sender.RequestText = $"{SmtpCommandNames.RCPT} TO:<{recipient}>";
|
||||
|
||||
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);
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
sender.ValidateReply();
|
||||
}
|
||||
|
||||
{
|
||||
// CONTENT
|
||||
String dataTerminator = sessionState.DataBuffer
|
||||
.Skip(sessionState.DataBuffer.Count - 5)
|
||||
.ToText();
|
||||
|
||||
sender.RequestText = $"Buffer ({sessionState.DataBuffer.Count} bytes)";
|
||||
|
||||
await connection.WriteDataAsync(sessionState.DataBuffer.ToArray(), true, ct).ConfigureAwait(false);
|
||||
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);
|
||||
sender.ValidateReply();
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
String 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);
|
||||
|
||||
throw new SmtpException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendEhlo(CancellationToken ct, SmtpSender sender, Connection connection) {
|
||||
sender.RequestText = $"{SmtpCommandNames.EHLO} {this.ClientHostname}";
|
||||
|
||||
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
|
||||
|
||||
do {
|
||||
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
} while(!sender.IsReplyOk);
|
||||
|
||||
sender.ValidateReply();
|
||||
}
|
||||
|
||||
private class ConnectionAuth {
|
||||
private readonly SmtpSender _sender;
|
||||
private readonly Connection _connection;
|
||||
private readonly NetworkCredential _credentials;
|
||||
|
||||
public ConnectionAuth(Connection connection, SmtpSender sender, NetworkCredential credentials) {
|
||||
this._connection = connection;
|
||||
this._sender = sender;
|
||||
this._credentials = credentials;
|
||||
}
|
||||
|
||||
public async Task AuthenticateAsync(CancellationToken ct) {
|
||||
this._sender.RequestText =
|
||||
$"{SmtpCommandNames.AUTH} {SmtpDefinitions.SmtpAuthMethods.Login} {Convert.ToBase64String(Encoding.UTF8.GetBytes(this._credentials.UserName))}";
|
||||
|
||||
await this._connection.WriteLineAsync(this._sender.RequestText, ct).ConfigureAwait(false);
|
||||
this._sender.ReplyText = await this._connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
this._sender.ValidateReply();
|
||||
this._sender.RequestText = Convert.ToBase64String(Encoding.UTF8.GetBytes(this._credentials.Password));
|
||||
|
||||
await this._connection.WriteLineAsync(this._sender.RequestText, ct).ConfigureAwait(false);
|
||||
this._sender.ReplyText = await this._connection.ReadLineAsync(ct).ConfigureAwait(false);
|
||||
this._sender.ValidateReply();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// Contains useful constants and definitions.
|
||||
/// The string sequence that delimits the end of the DATA command.
|
||||
/// </summary>
|
||||
public static class SmtpDefinitions
|
||||
{
|
||||
/// <summary>
|
||||
/// The string sequence that delimits the end of the DATA command.
|
||||
/// </summary>
|
||||
public const string SmtpDataCommandTerminator = "\r\n.\r\n";
|
||||
|
||||
/// <summary>
|
||||
/// Lists the AUTH methods supported by default.
|
||||
/// </summary>
|
||||
public static class SmtpAuthMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// The plain method.
|
||||
/// </summary>
|
||||
public const string Plain = "PLAIN";
|
||||
|
||||
/// <summary>
|
||||
/// The login method.
|
||||
/// </summary>
|
||||
public const string Login = "LOGIN";
|
||||
}
|
||||
}
|
||||
public const String SmtpDataCommandTerminator = "\r\n.\r\n";
|
||||
|
||||
/// <summary>
|
||||
/// Lists the AUTH methods supported by default.
|
||||
/// </summary>
|
||||
public static class SmtpAuthMethods {
|
||||
/// <summary>
|
||||
/// The plain method.
|
||||
/// </summary>
|
||||
public const String Plain = "PLAIN";
|
||||
|
||||
/// <summary>
|
||||
/// The login method.
|
||||
/// </summary>
|
||||
public const String Login = "LOGIN";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +1,55 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System;
|
||||
#if !NETSTANDARD1_3
|
||||
using System.Net.Mail;
|
||||
using System.Net.Mail;
|
||||
#else
|
||||
using Exceptions;
|
||||
using Exceptions;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Use this class to store the sender session data.
|
||||
/// </summary>
|
||||
internal class SmtpSender
|
||||
{
|
||||
private readonly string _sessionId;
|
||||
private string _requestText;
|
||||
|
||||
public SmtpSender(string sessionId)
|
||||
{
|
||||
_sessionId = sessionId;
|
||||
}
|
||||
|
||||
public string RequestText
|
||||
{
|
||||
get => _requestText;
|
||||
set
|
||||
{
|
||||
_requestText = value;
|
||||
$" TX {_requestText}".Debug(typeof(SmtpClient), _sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public string ReplyText { get; set; }
|
||||
|
||||
public bool IsReplyOk => ReplyText.StartsWith("250 ");
|
||||
|
||||
public void ValidateReply()
|
||||
{
|
||||
if (ReplyText == null)
|
||||
throw new SmtpException("There was no response from the server");
|
||||
|
||||
try
|
||||
{
|
||||
var response = SmtpServerReply.Parse(ReplyText);
|
||||
$" RX {ReplyText} - {response.IsPositive}".Debug(typeof(SmtpClient), _sessionId);
|
||||
|
||||
if (response.IsPositive) return;
|
||||
|
||||
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($"Could not parse server response: {ReplyText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <summary>
|
||||
/// Use this class to store the sender session data.
|
||||
/// </summary>
|
||||
internal class SmtpSender {
|
||||
private readonly String _sessionId;
|
||||
private String _requestText;
|
||||
|
||||
public SmtpSender(String sessionId) => this._sessionId = sessionId;
|
||||
|
||||
public String RequestText {
|
||||
get => this._requestText;
|
||||
set {
|
||||
this._requestText = value;
|
||||
$" TX {this._requestText}".Debug(typeof(SmtpClient), this._sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public String ReplyText {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Boolean IsReplyOk => this.ReplyText.StartsWith("250 ");
|
||||
|
||||
public void ValidateReply() {
|
||||
if(this.ReplyText == null) {
|
||||
throw new SmtpException("There was no response from the server");
|
||||
}
|
||||
|
||||
try {
|
||||
SmtpServerReply response = SmtpServerReply.Parse(this.ReplyText);
|
||||
$" RX {this.ReplyText} - {response.IsPositive}".Debug(typeof(SmtpClient), this._sessionId);
|
||||
|
||||
if(response.IsPositive) {
|
||||
return;
|
||||
}
|
||||
|
||||
String 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($"Could not parse server response: {this.ReplyText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,243 +1,267 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <summary>
|
||||
/// Represents an SMTP server response object.
|
||||
/// </summary>
|
||||
public class SmtpServerReply {
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Represents an SMTP server response object.
|
||||
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
|
||||
/// </summary>
|
||||
public class SmtpServerReply
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
|
||||
/// </summary>
|
||||
/// <param name="responseCode">The response code.</param>
|
||||
/// <param name="statusCode">The status code.</param>
|
||||
/// <param name="content">The content.</param>
|
||||
public SmtpServerReply(int responseCode, string statusCode, params string[] content)
|
||||
{
|
||||
Content = new List<string>();
|
||||
ReplyCode = responseCode;
|
||||
EnhancedStatusCode = statusCode;
|
||||
Content.AddRange(content);
|
||||
IsValid = responseCode >= 200 && responseCode < 600;
|
||||
ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
|
||||
ReplyCodeCategory = SmtpReplyCodeCategories.Unknown;
|
||||
|
||||
if (!IsValid) return;
|
||||
if (responseCode >= 200) ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion;
|
||||
if (responseCode >= 300) ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate;
|
||||
if (responseCode >= 400) ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative;
|
||||
if (responseCode >= 500) ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative;
|
||||
if (responseCode >= 600) ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
|
||||
|
||||
if (int.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out var middleDigit))
|
||||
{
|
||||
if (middleDigit >= 0 && middleDigit <= 5)
|
||||
ReplyCodeCategory = (SmtpReplyCodeCategories) middleDigit;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
|
||||
/// </summary>
|
||||
public SmtpServerReply()
|
||||
: this(0, string.Empty, string.Empty)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
|
||||
/// </summary>
|
||||
/// <param name="responseCode">The response code.</param>
|
||||
/// <param name="statusCode">The status code.</param>
|
||||
/// <param name="content">The content.</param>
|
||||
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>
|
||||
/// <param name="responseCode">The response code.</param>
|
||||
/// <param name="content">The content.</param>
|
||||
public SmtpServerReply(int responseCode, string content)
|
||||
: this(responseCode, string.Empty, content)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pre-built responses (https://tools.ietf.org/html/rfc5321#section-4.2.2)
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command unrecognized reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply CommandUnrecognized =>
|
||||
new SmtpServerReply(500, "Syntax error, command unrecognized");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the syntax error arguments reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply SyntaxErrorArguments =>
|
||||
new SmtpServerReply(501, "Syntax error in parameters or arguments");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command not implemented reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply CommandNotImplemented => new SmtpServerReply(502, "Command not implemented");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bad sequence of commands reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply BadSequenceOfCommands => new SmtpServerReply(503, "Bad sequence of commands");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the protocol violation reply.
|
||||
/// </summary>=
|
||||
public static SmtpServerReply ProtocolViolation =>
|
||||
new SmtpServerReply(451, "Requested action aborted: error in processing");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system status bye reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply SystemStatusBye =>
|
||||
new SmtpServerReply(221, "Service closing transmission channel");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system status help reply.
|
||||
/// </summary>=
|
||||
public static SmtpServerReply SystemStatusHelp => new SmtpServerReply(221, "Refer to RFC 5321");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bad syntax command empty reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply BadSyntaxCommandEmpty => new SmtpServerReply(400, "Error: bad syntax");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OK reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply Ok => new SmtpServerReply(250, "OK");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authorization required reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply AuthorizationRequired => new SmtpServerReply(530, "Authorization Required");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response severity.
|
||||
/// </summary>
|
||||
public SmtpReplyCodeSeverities ReplyCodeSeverity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response category.
|
||||
/// </summary>
|
||||
public SmtpReplyCodeCategories ReplyCodeCategory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the numeric response code.
|
||||
/// </summary>
|
||||
public int ReplyCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enhanced status code.
|
||||
/// </summary>
|
||||
public string EnhancedStatusCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content.
|
||||
/// </summary>
|
||||
public List<string> Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the response code is between 200 and 599.
|
||||
/// </summary>
|
||||
public bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is positive.
|
||||
/// </summary>
|
||||
public bool IsPositive => ReplyCode >= 200 && ReplyCode <= 399;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified text into a Server Reply for thorough analysis.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <returns>A new instance of SMTP server response object.</returns>
|
||||
public static SmtpServerReply Parse(string text)
|
||||
{
|
||||
var lines = text.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lines.Length == 0) return new SmtpServerReply();
|
||||
|
||||
var lastLineParts = lines.Last().Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
|
||||
var enhancedStatusCode = string.Empty;
|
||||
int.TryParse(lastLineParts[0], out var responseCode);
|
||||
if (lastLineParts.Length > 1)
|
||||
{
|
||||
if (lastLineParts[1].Split('.').Length == 3)
|
||||
enhancedStatusCode = lastLineParts[1];
|
||||
}
|
||||
|
||||
var content = new List<string>();
|
||||
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var splitChar = i == lines.Length - 1 ? " " : "-";
|
||||
|
||||
var lineParts = lines[i].Split(new[] {splitChar}, 2, StringSplitOptions.None);
|
||||
var lineContent = lineParts.Last();
|
||||
if (string.IsNullOrWhiteSpace(enhancedStatusCode) == false)
|
||||
lineContent = lineContent.Replace(enhancedStatusCode, string.Empty).Trim();
|
||||
|
||||
content.Add(lineContent);
|
||||
}
|
||||
|
||||
return new SmtpServerReply(responseCode, enhancedStatusCode, content.ToArray());
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
var responseCodeText = ReplyCode.ToString(CultureInfo.InvariantCulture);
|
||||
var statusCodeText = string.IsNullOrWhiteSpace(EnhancedStatusCode)
|
||||
? string.Empty
|
||||
: $" {EnhancedStatusCode.Trim()}";
|
||||
if (Content.Count == 0) return $"{responseCodeText}{statusCodeText}";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
for (var i = 0; i < Content.Count; i++)
|
||||
{
|
||||
var isLastLine = i == Content.Count - 1;
|
||||
|
||||
builder.Append(isLastLine
|
||||
? $"{responseCodeText}{statusCodeText} {Content[i]}"
|
||||
: $"{responseCodeText}-{Content[i]}\r\n");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
/// <param name="responseCode">The response code.</param>
|
||||
/// <param name="statusCode">The status code.</param>
|
||||
/// <param name="content">The content.</param>
|
||||
public SmtpServerReply(Int32 responseCode, String statusCode, params String[] content) {
|
||||
this.Content = new List<String>();
|
||||
this.ReplyCode = responseCode;
|
||||
this.EnhancedStatusCode = statusCode;
|
||||
this.Content.AddRange(content);
|
||||
this.IsValid = responseCode >= 200 && responseCode < 600;
|
||||
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
|
||||
this.ReplyCodeCategory = SmtpReplyCodeCategories.Unknown;
|
||||
|
||||
if(!this.IsValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(responseCode >= 200) {
|
||||
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion;
|
||||
}
|
||||
|
||||
if(responseCode >= 300) {
|
||||
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate;
|
||||
}
|
||||
|
||||
if(responseCode >= 400) {
|
||||
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative;
|
||||
}
|
||||
|
||||
if(responseCode >= 500) {
|
||||
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative;
|
||||
}
|
||||
|
||||
if(responseCode >= 600) {
|
||||
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
|
||||
}
|
||||
|
||||
if(Int32.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out Int32 middleDigit)) {
|
||||
if(middleDigit >= 0 && middleDigit <= 5) {
|
||||
this.ReplyCodeCategory = (SmtpReplyCodeCategories)middleDigit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
|
||||
/// </summary>
|
||||
public SmtpServerReply()
|
||||
: this(0, String.Empty, String.Empty) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
|
||||
/// </summary>
|
||||
/// <param name="responseCode">The response code.</param>
|
||||
/// <param name="statusCode">The status code.</param>
|
||||
/// <param name="content">The content.</param>
|
||||
public SmtpServerReply(Int32 responseCode, String statusCode, String content)
|
||||
: this(responseCode, statusCode, new[] { content }) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
|
||||
/// </summary>
|
||||
/// <param name="responseCode">The response code.</param>
|
||||
/// <param name="content">The content.</param>
|
||||
public SmtpServerReply(Int32 responseCode, String content)
|
||||
: this(responseCode, String.Empty, content) {
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pre-built responses (https://tools.ietf.org/html/rfc5321#section-4.2.2)
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command unrecognized reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply CommandUnrecognized =>
|
||||
new SmtpServerReply(500, "Syntax error, command unrecognized");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the syntax error arguments reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply SyntaxErrorArguments =>
|
||||
new SmtpServerReply(501, "Syntax error in parameters or arguments");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command not implemented reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply CommandNotImplemented => new SmtpServerReply(502, "Command not implemented");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bad sequence of commands reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply BadSequenceOfCommands => new SmtpServerReply(503, "Bad sequence of commands");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the protocol violation reply.
|
||||
/// </summary>=
|
||||
public static SmtpServerReply ProtocolViolation =>
|
||||
new SmtpServerReply(451, "Requested action aborted: error in processing");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system status bye reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply SystemStatusBye =>
|
||||
new SmtpServerReply(221, "Service closing transmission channel");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system status help reply.
|
||||
/// </summary>=
|
||||
public static SmtpServerReply SystemStatusHelp => new SmtpServerReply(221, "Refer to RFC 5321");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bad syntax command empty reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply BadSyntaxCommandEmpty => new SmtpServerReply(400, "Error: bad syntax");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OK reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply Ok => new SmtpServerReply(250, "OK");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authorization required reply.
|
||||
/// </summary>
|
||||
public static SmtpServerReply AuthorizationRequired => new SmtpServerReply(530, "Authorization Required");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response severity.
|
||||
/// </summary>
|
||||
public SmtpReplyCodeSeverities ReplyCodeSeverity {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response category.
|
||||
/// </summary>
|
||||
public SmtpReplyCodeCategories ReplyCodeCategory {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the numeric response code.
|
||||
/// </summary>
|
||||
public Int32 ReplyCode {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enhanced status code.
|
||||
/// </summary>
|
||||
public String EnhancedStatusCode {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content.
|
||||
/// </summary>
|
||||
public List<String> Content {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the response code is between 200 and 599.
|
||||
/// </summary>
|
||||
public Boolean IsValid {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is positive.
|
||||
/// </summary>
|
||||
public Boolean IsPositive => this.ReplyCode >= 200 && this.ReplyCode <= 399;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified text into a Server Reply for thorough analysis.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <returns>A new instance of SMTP server response object.</returns>
|
||||
public static SmtpServerReply Parse(String text) {
|
||||
String[] lines = text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if(lines.Length == 0) {
|
||||
return new SmtpServerReply();
|
||||
}
|
||||
|
||||
String[] lastLineParts = lines.Last().Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
String enhancedStatusCode = String.Empty;
|
||||
_ = Int32.TryParse(lastLineParts[0], out Int32 responseCode);
|
||||
if(lastLineParts.Length > 1) {
|
||||
if(lastLineParts[1].Split('.').Length == 3) {
|
||||
enhancedStatusCode = lastLineParts[1];
|
||||
}
|
||||
}
|
||||
|
||||
List<String> content = new List<String>();
|
||||
|
||||
for(Int32 i = 0; i < lines.Length; i++) {
|
||||
String splitChar = i == lines.Length - 1 ? " " : "-";
|
||||
|
||||
String[] lineParts = lines[i].Split(new[] { splitChar }, 2, StringSplitOptions.None);
|
||||
String lineContent = lineParts.Last();
|
||||
if(String.IsNullOrWhiteSpace(enhancedStatusCode) == false) {
|
||||
lineContent = lineContent.Replace(enhancedStatusCode, String.Empty).Trim();
|
||||
}
|
||||
|
||||
content.Add(lineContent);
|
||||
}
|
||||
|
||||
return new SmtpServerReply(responseCode, enhancedStatusCode, content.ToArray());
|
||||
}
|
||||
|
||||
/// <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() {
|
||||
String responseCodeText = this.ReplyCode.ToString(CultureInfo.InvariantCulture);
|
||||
String statusCodeText = String.IsNullOrWhiteSpace(this.EnhancedStatusCode)
|
||||
? 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
|
||||
}
|
||||
}
|
@ -1,162 +1,183 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System;
|
||||
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>
|
||||
/// Represents the state of an SMTP session associated with a client.
|
||||
/// Initializes a new instance of the <see cref="SmtpSessionState"/> class.
|
||||
/// </summary>
|
||||
public class SmtpSessionState
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmtpSessionState"/> class.
|
||||
/// </summary>
|
||||
public SmtpSessionState()
|
||||
{
|
||||
DataBuffer = new List<byte>();
|
||||
Reset(true);
|
||||
ResetAuthentication();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contents of the data buffer.
|
||||
/// </summary>
|
||||
public List<byte> DataBuffer { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has initiated.
|
||||
/// </summary>
|
||||
public bool HasInitiated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public string ClientHostname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the session is currently receiving DATA.
|
||||
/// </summary>
|
||||
public bool IsInDataMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sender address.
|
||||
/// </summary>
|
||||
public string SenderAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recipients.
|
||||
/// </summary>
|
||||
public List<string> Recipients { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extended data supporting any additional field for storage by a responder implementation.
|
||||
/// </summary>
|
||||
public object ExtendedData { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region AUTH State
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is in authentication mode.
|
||||
/// </summary>
|
||||
public bool IsInAuthMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the username.
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password.
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has provided username.
|
||||
/// </summary>
|
||||
public bool HasProvidedUsername => string.IsNullOrWhiteSpace(Username) == false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is authenticated.
|
||||
/// </summary>
|
||||
public bool IsAuthenticated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication mode.
|
||||
/// </summary>
|
||||
public string AuthMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is channel secure.
|
||||
/// </summary>
|
||||
public bool IsChannelSecure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resets the authentication state.
|
||||
/// </summary>
|
||||
public void ResetAuthentication()
|
||||
{
|
||||
Username = string.Empty;
|
||||
Password = string.Empty;
|
||||
AuthMode = string.Empty;
|
||||
IsInAuthMode = false;
|
||||
IsAuthenticated = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Resets the data mode to false, clears the recipients, the sender address and the data buffer.
|
||||
/// </summary>
|
||||
public void ResetEmail()
|
||||
{
|
||||
IsInDataMode = false;
|
||||
Recipients.Clear();
|
||||
SenderAddress = string.Empty;
|
||||
DataBuffer.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the state table entirely.
|
||||
/// </summary>
|
||||
/// <param name="clearExtensionData">if set to <c>true</c> [clear extension data].</param>
|
||||
public void Reset(bool clearExtensionData)
|
||||
{
|
||||
HasInitiated = false;
|
||||
SupportsExtensions = false;
|
||||
ClientHostname = string.Empty;
|
||||
ResetEmail();
|
||||
|
||||
if (clearExtensionData)
|
||||
ExtendedData = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new object that is a copy of the current instance.
|
||||
/// </summary>
|
||||
/// <returns>A clone.</returns>
|
||||
public virtual SmtpSessionState Clone()
|
||||
{
|
||||
var clonedState = this.CopyPropertiesToNew<SmtpSessionState>(new[] {nameof(DataBuffer)});
|
||||
clonedState.DataBuffer.AddRange(DataBuffer);
|
||||
clonedState.Recipients.AddRange(Recipients);
|
||||
|
||||
return clonedState;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
public SmtpSessionState() {
|
||||
this.DataBuffer = new List<Byte>();
|
||||
this.Reset(true);
|
||||
this.ResetAuthentication();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contents of the data buffer.
|
||||
/// </summary>
|
||||
public List<Byte> DataBuffer {
|
||||
get; protected set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has initiated.
|
||||
/// </summary>
|
||||
public Boolean HasInitiated {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the current session supports extensions.
|
||||
/// </summary>
|
||||
public Boolean SupportsExtensions {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the client hostname.
|
||||
/// </summary>
|
||||
public String ClientHostname {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the session is currently receiving DATA.
|
||||
/// </summary>
|
||||
public Boolean IsInDataMode {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sender address.
|
||||
/// </summary>
|
||||
public String SenderAddress {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recipients.
|
||||
/// </summary>
|
||||
public List<String> Recipients { get; } = new List<String>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extended data supporting any additional field for storage by a responder implementation.
|
||||
/// </summary>
|
||||
public Object ExtendedData {
|
||||
get; set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AUTH State
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is in authentication mode.
|
||||
/// </summary>
|
||||
public Boolean IsInAuthMode {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the username.
|
||||
/// </summary>
|
||||
public String Username {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password.
|
||||
/// </summary>
|
||||
public String Password {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has provided username.
|
||||
/// </summary>
|
||||
public Boolean HasProvidedUsername => String.IsNullOrWhiteSpace(this.Username) == false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is authenticated.
|
||||
/// </summary>
|
||||
public Boolean IsAuthenticated {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication mode.
|
||||
/// </summary>
|
||||
public String AuthMode {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is channel secure.
|
||||
/// </summary>
|
||||
public Boolean IsChannelSecure {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the authentication state.
|
||||
/// </summary>
|
||||
public void ResetAuthentication() {
|
||||
this.Username = String.Empty;
|
||||
this.Password = String.Empty;
|
||||
this.AuthMode = String.Empty;
|
||||
this.IsInAuthMode = false;
|
||||
this.IsAuthenticated = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Resets the data mode to false, clears the recipients, the sender address and the data buffer.
|
||||
/// </summary>
|
||||
public void ResetEmail() {
|
||||
this.IsInDataMode = false;
|
||||
this.Recipients.Clear();
|
||||
this.SenderAddress = String.Empty;
|
||||
this.DataBuffer.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the state table entirely.
|
||||
/// </summary>
|
||||
/// <param name="clearExtensionData">if set to <c>true</c> [clear extension data].</param>
|
||||
public void Reset(Boolean clearExtensionData) {
|
||||
this.HasInitiated = false;
|
||||
this.SupportsExtensions = false;
|
||||
this.ClientHostname = String.Empty;
|
||||
this.ResetEmail();
|
||||
|
||||
if(clearExtensionData) {
|
||||
this.ExtendedData = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
@ -1,37 +1,34 @@
|
||||
namespace Unosquare.Swan.Networking
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a little SNMP client based on http://www.java2s.com/Code/CSharp/Network/SimpleSNMP.htm.
|
||||
/// </summary>
|
||||
public static class SnmpClient
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Unosquare.Swan.Networking {
|
||||
/// <summary>
|
||||
/// Represents a little SNMP client based on http://www.java2s.com/Code/CSharp/Network/SimpleSNMP.htm.
|
||||
/// </summary>
|
||||
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,
|
||||
14, 48, 12, 6, 8, 43, 6, 1, 2, 1, 1, 1, 0, 5, 0,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Discovers the specified SNMP time out.
|
||||
/// </summary>
|
||||
/// <param name="snmpTimeOut">The SNMP time out.</param>
|
||||
/// <returns>An array of network endpoint as an IP address and a port number.</returns>
|
||||
public static IPEndPoint[] Discover(int snmpTimeOut = 6000)
|
||||
{
|
||||
var endpoints = new List<IPEndPoint>();
|
||||
|
||||
Task[] tasks =
|
||||
{
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Discovers the specified SNMP time out.
|
||||
/// </summary>
|
||||
/// <param name="snmpTimeOut">The SNMP time out.</param>
|
||||
/// <returns>An array of network endpoint as an IP address and a port number.</returns>
|
||||
public static IPEndPoint[] Discover(Int32 snmpTimeOut = 6000) {
|
||||
List<IPEndPoint> endpoints = new List<IPEndPoint>();
|
||||
|
||||
Task[] tasks =
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
using (var udp = new UdpClient(IPAddress.Broadcast.AddressFamily))
|
||||
using (UdpClient udp = new UdpClient(IPAddress.Broadcast.AddressFamily))
|
||||
{
|
||||
udp.EnableBroadcast = true;
|
||||
await udp.SendAsync(
|
||||
@ -43,7 +40,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var buffer = new byte[udp.Client.ReceiveBufferSize];
|
||||
Byte[] buffer = new Byte[udp.Client.ReceiveBufferSize];
|
||||
EndPoint remote = new IPEndPoint(IPAddress.Any, 0);
|
||||
udp.Client.ReceiveFrom(buffer, ref remote);
|
||||
endpoints.Add(remote as IPEndPoint);
|
||||
@ -59,222 +56,212 @@
|
||||
}
|
||||
}),
|
||||
Task.Delay(snmpTimeOut),
|
||||
};
|
||||
|
||||
Task.WaitAny(tasks);
|
||||
|
||||
return endpoints.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the public.
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <returns>
|
||||
/// A string that contains the results of decoding the specified sequence
|
||||
/// of bytes ref=GetString".
|
||||
/// </returns>
|
||||
public static string GetPublicName(IPEndPoint host) => GetString(host, "1.3.6.1.2.1.1.5.0");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the up-time.
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <param name="mibString">The mibString.</param>
|
||||
/// <returns>
|
||||
/// A time interval that represents a specified number of seconds,
|
||||
/// where the specification is accurate to the nearest millisecond.
|
||||
/// </returns>
|
||||
public static TimeSpan GetUptime(IPEndPoint host, string mibString = "1.3.6.1.2.1.1.3.0")
|
||||
{
|
||||
var response = Get(host, mibString);
|
||||
if (response[0] == 0xff) return TimeSpan.Zero;
|
||||
|
||||
// If response, get the community name and MIB lengths
|
||||
var commlength = Convert.ToInt16(response[6]);
|
||||
var miblength = Convert.ToInt16(response[23 + commlength]);
|
||||
|
||||
// Extract the MIB data from the SNMP response
|
||||
var datalength = Convert.ToInt16(response[25 + commlength + miblength]);
|
||||
var datastart = 26 + commlength + miblength;
|
||||
|
||||
var uptime = 0;
|
||||
|
||||
while (datalength > 0)
|
||||
{
|
||||
uptime = (uptime << 8) + response[datastart++];
|
||||
datalength--;
|
||||
}
|
||||
|
||||
return TimeSpan.FromSeconds(uptime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string.
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</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>
|
||||
public static string GetString(IPEndPoint host, string mibString)
|
||||
{
|
||||
var response = Get(host, mibString);
|
||||
if (response[0] == 0xff) return string.Empty;
|
||||
|
||||
// If response, get the community name and MIB lengths
|
||||
var commlength = Convert.ToInt16(response[6]);
|
||||
var miblength = Convert.ToInt16(response[23 + commlength]);
|
||||
|
||||
// Extract the MIB data from the SNMP response
|
||||
var datalength = Convert.ToInt16(response[25 + commlength + miblength]);
|
||||
var datastart = 26 + commlength + miblength;
|
||||
|
||||
return Encoding.ASCII.GetString(response, datastart, datalength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified host.
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <param name="mibString">The mibString.</param>
|
||||
/// <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>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <param name="community">The community.</param>
|
||||
/// <param name="mibString">The mibString.</param>
|
||||
/// <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];
|
||||
var mib = new byte[1024];
|
||||
var comlen = community.Length;
|
||||
var mibvals = mibString.Split('.');
|
||||
var miblen = mibvals.Length;
|
||||
var cnt = 0;
|
||||
var orgmiblen = miblen;
|
||||
var pos = 0;
|
||||
|
||||
// Convert the string MIB into a byte array of integer values
|
||||
// Unfortunately, values over 128 require multiple bytes
|
||||
// which also increases the MIB length
|
||||
for (var i = 0; i < orgmiblen; i++)
|
||||
{
|
||||
int temp = Convert.ToInt16(mibvals[i]);
|
||||
if (temp > 127)
|
||||
{
|
||||
mib[cnt] = Convert.ToByte(128 + (temp / 128));
|
||||
mib[cnt + 1] = Convert.ToByte(temp - ((temp / 128) * 128));
|
||||
cnt += 2;
|
||||
miblen++;
|
||||
}
|
||||
else
|
||||
{
|
||||
mib[cnt] = Convert.ToByte(temp);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
var snmplen = 29 + comlen + miblen - 1;
|
||||
|
||||
// The SNMP sequence start
|
||||
packet[pos++] = 0x30; // Sequence start
|
||||
packet[pos++] = Convert.ToByte(snmplen - 2); // sequence size
|
||||
|
||||
// SNMP version
|
||||
packet[pos++] = 0x02; // Integer type
|
||||
packet[pos++] = 0x01; // length
|
||||
packet[pos++] = 0x00; // SNMP version 1
|
||||
|
||||
// Community name
|
||||
packet[pos++] = 0x04; // String type
|
||||
packet[pos++] = Convert.ToByte(comlen); // length
|
||||
|
||||
// Convert community name to byte array
|
||||
var data = Encoding.ASCII.GetBytes(community);
|
||||
|
||||
foreach (var t in data)
|
||||
{
|
||||
packet[pos++] = t;
|
||||
}
|
||||
|
||||
// Add GetRequest or GetNextRequest value
|
||||
if (request == "get")
|
||||
packet[pos++] = 0xA0;
|
||||
else
|
||||
packet[pos++] = 0xA1;
|
||||
|
||||
packet[pos++] = Convert.ToByte(20 + miblen - 1); // Size of total MIB
|
||||
|
||||
// Request ID
|
||||
packet[pos++] = 0x02; // Integer type
|
||||
packet[pos++] = 0x04; // length
|
||||
packet[pos++] = 0x00; // SNMP request ID
|
||||
packet[pos++] = 0x00;
|
||||
packet[pos++] = 0x00;
|
||||
packet[pos++] = 0x01;
|
||||
|
||||
// Error status
|
||||
packet[pos++] = 0x02; // Integer type
|
||||
packet[pos++] = 0x01; // length
|
||||
packet[pos++] = 0x00; // SNMP error status
|
||||
|
||||
// Error index
|
||||
packet[pos++] = 0x02; // Integer type
|
||||
packet[pos++] = 0x01; // length
|
||||
packet[pos++] = 0x00; // SNMP error index
|
||||
|
||||
// Start of variable bindings
|
||||
packet[pos++] = 0x30; // Start of variable bindings sequence
|
||||
|
||||
packet[pos++] = Convert.ToByte(6 + miblen - 1); // Size of variable binding
|
||||
|
||||
packet[pos++] = 0x30; // Start of first variable bindings sequence
|
||||
packet[pos++] = Convert.ToByte(6 + miblen - 1 - 2); // size
|
||||
packet[pos++] = 0x06; // Object type
|
||||
packet[pos++] = Convert.ToByte(miblen - 1); // length
|
||||
|
||||
// Start of MIB
|
||||
packet[pos++] = 0x2b;
|
||||
|
||||
// Place MIB array in packet
|
||||
for (var i = 2; i < miblen; i++)
|
||||
packet[pos++] = Convert.ToByte(mib[i]);
|
||||
|
||||
packet[pos++] = 0x05; // Null object value
|
||||
packet[pos] = 0x00; // Null
|
||||
|
||||
// Send packet to destination
|
||||
SendPacket(host, packet, snmplen);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static void SendPacket(IPEndPoint host, byte[] packet, int length)
|
||||
{
|
||||
var sock = new Socket(
|
||||
AddressFamily.InterNetwork,
|
||||
SocketType.Dgram,
|
||||
ProtocolType.Udp);
|
||||
sock.SetSocketOption(
|
||||
SocketOptionLevel.Socket,
|
||||
SocketOptionName.ReceiveTimeout,
|
||||
5000);
|
||||
var ep = (EndPoint) host;
|
||||
sock.SendTo(packet, length, SocketFlags.None, host);
|
||||
|
||||
// Receive response from packet
|
||||
try
|
||||
{
|
||||
sock.ReceiveFrom(packet, ref ep);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
packet[0] = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Task.WaitAny(tasks);
|
||||
|
||||
return endpoints.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the public.
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <returns>
|
||||
/// A string that contains the results of decoding the specified sequence
|
||||
/// of bytes ref=GetString".
|
||||
/// </returns>
|
||||
public static String GetPublicName(IPEndPoint host) => GetString(host, "1.3.6.1.2.1.1.5.0");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the up-time.
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <param name="mibString">The mibString.</param>
|
||||
/// <returns>
|
||||
/// A time interval that represents a specified number of seconds,
|
||||
/// where the specification is accurate to the nearest millisecond.
|
||||
/// </returns>
|
||||
public static TimeSpan GetUptime(IPEndPoint host, String mibString = "1.3.6.1.2.1.1.3.0") {
|
||||
Byte[] response = Get(host, mibString);
|
||||
if(response[0] == 0xff) {
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
// If response, get the community name and MIB lengths
|
||||
Int16 commlength = Convert.ToInt16(response[6]);
|
||||
Int16 miblength = Convert.ToInt16(response[23 + commlength]);
|
||||
|
||||
// Extract the MIB data from the SNMP response
|
||||
Int16 datalength = Convert.ToInt16(response[25 + commlength + miblength]);
|
||||
Int32 datastart = 26 + commlength + miblength;
|
||||
|
||||
Int32 uptime = 0;
|
||||
|
||||
while(datalength > 0) {
|
||||
uptime = (uptime << 8) + response[datastart++];
|
||||
datalength--;
|
||||
}
|
||||
|
||||
return TimeSpan.FromSeconds(uptime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string.
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</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>
|
||||
public static String GetString(IPEndPoint host, String mibString) {
|
||||
Byte[] response = Get(host, mibString);
|
||||
if(response[0] == 0xff) {
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
// If response, get the community name and MIB lengths
|
||||
Int16 commlength = Convert.ToInt16(response[6]);
|
||||
Int16 miblength = Convert.ToInt16(response[23 + commlength]);
|
||||
|
||||
// Extract the MIB data from the SNMP response
|
||||
Int16 datalength = Convert.ToInt16(response[25 + commlength + miblength]);
|
||||
Int32 datastart = 26 + commlength + miblength;
|
||||
|
||||
return Encoding.ASCII.GetString(response, datastart, datalength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified host.
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <param name="mibString">The mibString.</param>
|
||||
/// <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>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <param name="community">The community.</param>
|
||||
/// <param name="mibString">The mibString.</param>
|
||||
/// <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) {
|
||||
Byte[] packet = new Byte[1024];
|
||||
Byte[] mib = new Byte[1024];
|
||||
Int32 comlen = community.Length;
|
||||
String[] mibvals = mibString.Split('.');
|
||||
Int32 miblen = mibvals.Length;
|
||||
Int32 cnt = 0;
|
||||
Int32 orgmiblen = miblen;
|
||||
Int32 pos = 0;
|
||||
|
||||
// Convert the string MIB into a byte array of integer values
|
||||
// Unfortunately, values over 128 require multiple bytes
|
||||
// which also increases the MIB length
|
||||
for(Int32 i = 0; i < orgmiblen; i++) {
|
||||
Int32 temp = Convert.ToInt16(mibvals[i]);
|
||||
if(temp > 127) {
|
||||
mib[cnt] = Convert.ToByte(128 + temp / 128);
|
||||
mib[cnt + 1] = Convert.ToByte(temp - temp / 128 * 128);
|
||||
cnt += 2;
|
||||
miblen++;
|
||||
} else {
|
||||
mib[cnt] = Convert.ToByte(temp);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
Int32 snmplen = 29 + comlen + miblen - 1;
|
||||
|
||||
// The SNMP sequence start
|
||||
packet[pos++] = 0x30; // Sequence start
|
||||
packet[pos++] = Convert.ToByte(snmplen - 2); // sequence size
|
||||
|
||||
// SNMP version
|
||||
packet[pos++] = 0x02; // Integer type
|
||||
packet[pos++] = 0x01; // length
|
||||
packet[pos++] = 0x00; // SNMP version 1
|
||||
|
||||
// Community name
|
||||
packet[pos++] = 0x04; // String type
|
||||
packet[pos++] = Convert.ToByte(comlen); // length
|
||||
|
||||
// Convert community name to byte array
|
||||
Byte[] data = Encoding.ASCII.GetBytes(community);
|
||||
|
||||
foreach(Byte t in data) {
|
||||
packet[pos++] = t;
|
||||
}
|
||||
|
||||
// Add GetRequest or GetNextRequest value
|
||||
packet[pos++] = request == "get" ? (Byte)0xA0 : (Byte)0xA1;
|
||||
|
||||
packet[pos++] = Convert.ToByte(20 + miblen - 1); // Size of total MIB
|
||||
|
||||
// Request ID
|
||||
packet[pos++] = 0x02; // Integer type
|
||||
packet[pos++] = 0x04; // length
|
||||
packet[pos++] = 0x00; // SNMP request ID
|
||||
packet[pos++] = 0x00;
|
||||
packet[pos++] = 0x00;
|
||||
packet[pos++] = 0x01;
|
||||
|
||||
// Error status
|
||||
packet[pos++] = 0x02; // Integer type
|
||||
packet[pos++] = 0x01; // length
|
||||
packet[pos++] = 0x00; // SNMP error status
|
||||
|
||||
// Error index
|
||||
packet[pos++] = 0x02; // Integer type
|
||||
packet[pos++] = 0x01; // length
|
||||
packet[pos++] = 0x00; // SNMP error index
|
||||
|
||||
// Start of variable bindings
|
||||
packet[pos++] = 0x30; // Start of variable bindings sequence
|
||||
|
||||
packet[pos++] = Convert.ToByte(6 + miblen - 1); // Size of variable binding
|
||||
|
||||
packet[pos++] = 0x30; // Start of first variable bindings sequence
|
||||
packet[pos++] = Convert.ToByte(6 + miblen - 1 - 2); // size
|
||||
packet[pos++] = 0x06; // Object type
|
||||
packet[pos++] = Convert.ToByte(miblen - 1); // length
|
||||
|
||||
// Start of MIB
|
||||
packet[pos++] = 0x2b;
|
||||
|
||||
// Place MIB array in packet
|
||||
for(Int32 i = 2; i < miblen; i++) {
|
||||
packet[pos++] = Convert.ToByte(mib[i]);
|
||||
}
|
||||
|
||||
packet[pos++] = 0x05; // Null object value
|
||||
packet[pos] = 0x00; // Null
|
||||
|
||||
// Send packet to destination
|
||||
SendPacket(host, packet, snmplen);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
|
||||
private static void SendPacket(IPEndPoint host, Byte[] packet, Int32 length) {
|
||||
Socket sock = new Socket(
|
||||
AddressFamily.InterNetwork,
|
||||
SocketType.Dgram,
|
||||
ProtocolType.Udp);
|
||||
sock.SetSocketOption(
|
||||
SocketOptionLevel.Socket,
|
||||
SocketOptionName.ReceiveTimeout,
|
||||
5000);
|
||||
EndPoint ep = (EndPoint)host;
|
||||
_ = sock.SendTo(packet, length, SocketFlags.None, host);
|
||||
|
||||
// Receive response from packet
|
||||
try {
|
||||
_ = sock.ReceiveFrom(packet, ref ep);
|
||||
} catch(SocketException) {
|
||||
packet[0] = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user