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