//******************************************************************************************************
// TlsClient.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 07/12/2012 - Stephen C. Wills
// Generated original version of source code.
// 12/13/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using GSF.Configuration;
using GSF.Diagnostics;
using GSF.IO;
using GSF.Net.Security;
using GSF.Threading;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
// ReSharper disable AccessToDisposedClosure
// ReSharper disable ArrangeAccessorOwnerBody
namespace GSF.Communication
{
///
/// Represents a TCP-based communication client with SSL authentication and encryption.
///
///
public class TlsClient : ClientBase
{
#region [ Members ]
// Nested Types
private sealed class ConnectState : IDisposable
{
public Socket Socket;
public NetworkStream NetworkStream;
public SslStream SslStream;
public NegotiateStream NegotiateStream;
public readonly SocketAsyncEventArgs ConnectArgs = new SocketAsyncEventArgs();
public int ConnectionAttempts;
public readonly CancellationToken Token = new CancellationToken();
public ICancellationToken TimeoutToken;
public void Dispose()
{
ConnectArgs.Dispose();
Socket?.Dispose();
NetworkStream?.Dispose();
SslStream?.Dispose();
NegotiateStream?.Dispose();
}
}
private sealed class ReceiveState : IDisposable
{
public Socket Socket;
public NetworkStream NetworkStream;
public SslStream SslStream;
public byte[] Buffer;
public int Offset;
public int PayloadLength = -1;
public CancellationToken Token;
public void Dispose()
{
Dispose(Socket);
Dispose(NetworkStream);
Dispose(SslStream);
}
private static void Dispose(IDisposable obj) => obj?.Dispose();
}
private sealed class SendState : IDisposable
{
public Socket Socket;
public NetworkStream NetworkStream;
public SslStream SslStream;
public readonly ConcurrentQueue SendQueue = new ConcurrentQueue();
public TlsClientPayload Payload;
public int Sending;
public CancellationToken Token;
public void Dispose()
{
Dispose(Socket);
Dispose(NetworkStream);
Dispose(SslStream);
while (SendQueue.TryDequeue(out TlsClientPayload payload))
{
payload.WaitHandle.Set();
payload.WaitHandle.Dispose();
}
}
private static void Dispose(IDisposable obj) => obj?.Dispose();
}
private class TlsClientPayload
{
public byte[] Data;
public int Offset;
public int Length;
public ManualResetEvent WaitHandle;
}
private class CancellationToken
{
private int m_cancelled;
public bool Cancelled => Interlocked.CompareExchange(ref m_cancelled, 0, 0) != 0;
public bool Cancel() => Interlocked.Exchange(ref m_cancelled, 1) != 0;
}
// Constants
///
/// Specifies the default value for the property.
///
public readonly string DefaultTrustedCertificatesPath = FilePath.GetAbsolutePath(@"Certs\Remotes");
///
/// Specifies the default value for the property.
///
public const bool DefaultPayloadAware = false;
///
/// Specifies the default value for the property.
///
public const bool DefaultIntegratedSecurity = false;
///
/// Specifies the default value for the property.
///
public const bool DefaultIgnoreInvalidCredentials = false;
///
/// Specifies the default value for the property.
///
public const bool DefaultAllowDualStackSocket = true;
///
/// Specifies the default value for the property.
///
public const int DefaultMaxSendQueueSize = 500000;
///
/// Specifies the default value for the property.
///
public const bool DefaultNoDelay = false;
///
/// Specifies the default value for the property.
///
public const string DefaultConnectionString = "Server=localhost:8888";
// Fields
private readonly SimpleCertificateChecker m_defaultCertificateChecker;
private ICertificateChecker m_certificateChecker;
private readonly X509Certificate2Collection m_clientCertificates;
private SslProtocols m_enabledSslProtocols;
private string m_certificateFile;
private byte[] m_payloadMarker;
private EndianOrder m_payloadEndianOrder;
private IPStack m_ipStack;
private readonly ShortSynchronizedOperation m_dumpPayloadsOperation;
private string[] m_serverList;
private Dictionary m_connectData;
private ManualResetEvent m_connectWaitHandle;
private ConnectState m_connectState;
private ReceiveState m_receiveState;
private SendState m_sendState;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the class.
///
public TlsClient() : this(DefaultConnectionString)
{
}
///
/// Initializes a new instance of the class.
///
/// Connect string of the . See for format.
public TlsClient(string connectString) : base(TransportProtocol.Tcp, connectString)
{
m_defaultCertificateChecker = new SimpleCertificateChecker();
LocalCertificateSelectionCallback = DefaultLocalCertificateSelectionCallback;
m_clientCertificates = new X509Certificate2Collection();
m_enabledSslProtocols = SslProtocols.Tls12;
CheckCertificateRevocation = true;
TrustedCertificatesPath = DefaultTrustedCertificatesPath;
PayloadAware = DefaultPayloadAware;
m_payloadMarker = Payload.DefaultMarker;
m_payloadEndianOrder = EndianOrder.LittleEndian;
IntegratedSecurity = DefaultIntegratedSecurity;
IgnoreInvalidCredentials = DefaultIgnoreInvalidCredentials;
AllowDualStackSocket = DefaultAllowDualStackSocket;
MaxSendQueueSize = DefaultMaxSendQueueSize;
NoDelay = DefaultNoDelay;
m_dumpPayloadsOperation = new ShortSynchronizedOperation(DumpPayloads, OnSendDataException);
}
///
/// Initializes a new instance of the class.
///
/// object that contains the .
public TlsClient(IContainer container) : this() => container?.Add(this);
#endregion
#region [ Properties ]
///
/// Gets or sets a boolean value that indicates whether the payload boundaries are to be preserved during transmission.
///
[Category("Data")]
[DefaultValue(DefaultPayloadAware)]
[Description("Indicates whether the payload boundaries are to be preserved during transmission.")]
public bool PayloadAware { get; set; }
///
/// Gets or sets the byte sequence used to mark the beginning of a payload in a transmission.
///
///
/// Setting property to null will create a zero-length payload marker.
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public byte[] PayloadMarker
{
get => m_payloadMarker;
set => m_payloadMarker = value ?? Array.Empty();
}
///
/// Gets or sets the endian order to apply for encoding and decoding payload size in a transmission.
///
///
/// Setting property to null will force use of little-endian encoding.
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public EndianOrder PayloadEndianOrder
{
get => m_payloadEndianOrder;
set => m_payloadEndianOrder = value ?? EndianOrder.LittleEndian;
}
///
/// Gets or sets a boolean value that indicates whether the current Windows account credentials are used for authentication.
///
///
/// This option is ignored under Mono deployments.
///
[Category("Security")]
[DefaultValue(DefaultIntegratedSecurity)]
[Description("Indicates whether the current Windows account credentials are used for authentication.")]
public bool IntegratedSecurity { get; set; }
///
/// Gets or sets a boolean value that indicates whether the server
/// should ignore errors when the client's credentials are invalid.
///
///
/// This property should only be set to true if there is an alternative by which
/// to authenticate the client when integrated security fails.
///
[Category("Security")]
[DefaultValue(DefaultIgnoreInvalidCredentials)]
[Description("Indicates whether the client Windows account credentials are validated during authentication.")]
public bool IgnoreInvalidCredentials { get; set; }
///
/// Gets or sets a boolean value that determines if dual-mode socket is allowed when endpoint address is IPv6.
///
[Category("Settings")]
[DefaultValue(DefaultAllowDualStackSocket)]
[Description("Determines if dual-mode socket is allowed when endpoint address is IPv6.")]
public bool AllowDualStackSocket { get; set; }
///
/// Gets or sets the maximum size for the send queue before payloads are dumped from the queue.
///
[Category("Settings")]
[DefaultValue(DefaultMaxSendQueueSize)]
[Description("The maximum size for the send queue before payloads are dumped from the queue.")]
public int MaxSendQueueSize { get; set; }
///
/// Gets or sets a boolean value that determines if small packets are delivered to the remote host without delay.
///
[Category("Settings")]
[DefaultValue(DefaultNoDelay)]
[Description("Determines if small packets are delivered to the remote host without delay.")]
public bool NoDelay { get; set; }
///
/// Gets the object for the .
///
[Browsable(false)]
public Socket Client => m_connectState?.Socket;
///
/// Gets the object for the .
///
[Browsable(false)]
public SslStream SslStream => m_connectState?.SslStream;
///
/// Gets the server URI of the .
///
[Browsable(false)]
public override string ServerUri => $"{TransportProtocol}://{ServerList[ServerIndex]}".ToLower();
///
/// Gets or sets network credential that is used when
/// is set to true.
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public NetworkCredential NetworkCredential { get; set; }
///
/// Gets or sets the certificate checker used to validate remote certificates.
///
///
/// The certificate checker will only be used to validate certificates if
/// the is set to null.
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ICertificateChecker CertificateChecker
{
get => m_certificateChecker ?? m_defaultCertificateChecker;
set => m_certificateChecker = value;
}
///
/// Gets or sets the callback used to verify remote certificates.
///
///
/// Setting this property overrides the validation
/// callback in the .
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
///
/// Gets or sets the callback used to select a local certificate.
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public LocalCertificateSelectionCallback LocalCertificateSelectionCallback { get; set; }
///
/// Gets the collection of X509 certificates for this client.
///
[Browsable(false)]
public X509CertificateCollection ClientCertificates => m_clientCertificates;
///
/// Gets or sets a set of flags which determine the enabled .
///
/// Failed to write event log entry for security warning about use of less secure TLS/SSL protocols.
[Category("Settings")]
[DefaultValue(SslProtocols.Tls12)]
[Description("The set of SSL protocols that are enabled for this client.")]
public SslProtocols EnabledSslProtocols
{
get => m_enabledSslProtocols;
set
{
m_enabledSslProtocols = value;
// As of July 2014, Tls12 is the latest and most secure TLS protocol - all other protocols
// represent a security risk when used, as such we log a security message when any of the
// older protocols are being used.
if (m_enabledSslProtocols >= SslProtocols.Tls12)
return;
try
{
string applicationName;
// Get application name
try
{
// Attempt to retrieve application name as defined in common security settings - this name
// is typically preconfigured as the desired event source for event log entries
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings["SecurityProvider"];
applicationName = settings["ApplicationName"].Value;
}
catch
{
applicationName = null;
}
// Fall back on running executable name
if (string.IsNullOrWhiteSpace(applicationName))
applicationName = FilePath.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName);
string message = $"One or more less secure TLS/SSL protocols \"{m_enabledSslProtocols}\" are being used by an instance of the TlsClient in {applicationName}";
EventLog.WriteEntry(applicationName, message, EventLogEntryType.Warning, 1);
}
catch (Exception ex)
{
throw new SecurityException($"Failed to write event log entry for security warning about use of less secure TLS/SSL protocols \"{m_enabledSslProtocols}\": {ex.Message}", ex);
}
}
}
///
/// Gets or sets a boolean value that determines whether the certificate revocation list is checked during authentication.
///
[Category("Settings")]
[DefaultValue(true)]
[Description("True if the certificate revocation list is to be checked during authentication, otherwise False.")]
public bool CheckCertificateRevocation { get; set; }
///
/// Gets or sets the path to the certificate used for authentication.
///
[Category("Settings")]
[DefaultValue(null)]
[Description("Path to the certificate used by this client for authentication.")]
public string CertificateFile
{
get => m_certificateFile;
set
{
if (string.IsNullOrWhiteSpace(value))
{
m_certificateFile = null;
Certificate = null;
}
else
{
m_certificateFile = FilePath.GetAbsolutePath(value);
if (File.Exists(m_certificateFile))
Certificate = new X509Certificate2(m_certificateFile);
}
}
}
///
/// Gets or sets the local certificate selected by the default .
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public X509Certificate Certificate { get; set; }
///
/// Gets or sets the path to the directory containing the trusted certificates.
///
[Category("Settings")]
[DefaultValue("Trusted Certificates")]
[Description("Path to the directory containing the trusted remote certificates.")]
public string TrustedCertificatesPath { get; set; }
///
/// Gets or sets the set of valid policy errors when validating remote certificates.
///
[Category("Settings")]
[DefaultValue(SslPolicyErrors.None)]
[Description("Set of valid policy errors when validating remote certificates.")]
public SslPolicyErrors ValidPolicyErrors
{
get => m_defaultCertificateChecker.ValidPolicyErrors;
set => m_defaultCertificateChecker.ValidPolicyErrors = value;
}
///
/// Gets or sets the set of valid chain flags used when validating remote certificates.
///
[Category("Settings")]
[DefaultValue(X509ChainStatusFlags.NoError)]
[Description("Set of valid chain flags used when validating remote certificates.")]
public X509ChainStatusFlags ValidChainFlags
{
get => m_defaultCertificateChecker.ValidChainFlags;
set => m_defaultCertificateChecker.ValidChainFlags = value;
}
///
/// Determines whether the base class should track statistics.
///
protected override bool TrackStatistics => false;
// Gets server connect data as an array - will always be at least one empty string, not null
private string[] ServerList
{
get
{
if (!(m_serverList is null))
return m_serverList;
if (m_connectData is null || !m_connectData.TryGetValue("server", out string serverList) || string.IsNullOrWhiteSpace(serverList))
return Array.Empty();
return m_serverList = serverList.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(server => server.Trim()).ToArray();
}
}
///
/// Gets the descriptive status of the client.
///
public override string Status
{
get
{
SendState sendState = m_sendState;
StringBuilder statusBuilder = new StringBuilder(base.Status);
if (sendState != null)
{
statusBuilder.AppendFormat(" Queued payloads: {0}", sendState.SendQueue?.Count ?? 0);
statusBuilder.AppendLine();
}
return statusBuilder.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Saves settings to the config file if the property is set to true.
///
public override void SaveSettings()
{
base.SaveSettings();
if (!PersistSettings)
return;
// Save settings under the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory];
settings["EnabledSslProtocols", true].Update(m_enabledSslProtocols);
settings["CheckCertificateRevocation", true].Update(CheckCertificateRevocation);
settings["CertificateFile", true].Update(m_certificateFile);
settings["TrustedCertificatesPath", true].Update(TrustedCertificatesPath);
settings["ValidPolicyErrors", true].Update(ValidPolicyErrors);
settings["ValidChainFlags", true].Update(ValidChainFlags);
settings["PayloadAware", true].Update(PayloadAware);
settings["IntegratedSecurity", true].Update(IntegratedSecurity);
settings["AllowDualStackSocket", true].Update(AllowDualStackSocket);
settings["MaxSendQueueSize", true].Update(MaxSendQueueSize);
settings["NoDelay", true].Update(NoDelay);
config.Save();
}
///
/// Loads saved settings from the config file if the property is set to true.
///
public override void LoadSettings()
{
base.LoadSettings();
if (PersistSettings)
{
// Load settings from the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory];
settings.Add("EnabledSslProtocols", m_enabledSslProtocols, "The set of SSL protocols that are enabled for this client.");
settings.Add("CheckCertificateRevocation", CheckCertificateRevocation, "True if the certificate revocation list is to be checked during authentication, otherwise False.");
settings.Add("CertificateFile", m_certificateFile, "Path to the certificate used by this client for authentication.");
settings.Add("TrustedCertificatesPath", TrustedCertificatesPath, "Path to the directory containing the trusted remote certificates.");
settings.Add("ValidPolicyErrors", ValidPolicyErrors, "Set of valid policy errors when validating remote certificates.");
settings.Add("ValidChainFlags", ValidChainFlags, "Set of valid chain flags used when validating remote certificates.");
settings.Add("PayloadAware", PayloadAware, "True if payload boundaries are to be preserved during transmission, otherwise False.");
settings.Add("IntegratedSecurity", IntegratedSecurity, "True if the current Windows account credentials are used for authentication, otherwise False.");
settings.Add("AllowDualStackSocket", AllowDualStackSocket, "True if dual-mode socket is allowed when IP address is IPv6, otherwise False.");
settings.Add("MaxSendQueueSize", MaxSendQueueSize, "The maximum size of the send queue before payloads are dumped from the queue.");
settings.Add("NoDelay", NoDelay, "True to disable Nagle so that small packets are delivered to the remote host without delay, otherwise False.");
try
{
// Attempt to set desired transport security protocols
EnabledSslProtocols = settings["EnabledSslProtocols"].ValueAs(m_enabledSslProtocols);
}
catch (SecurityException ex)
{
// Security exception can occur when user forces use of older TLS protocol through configuration but event log warning entry cannot be written
OnConnectionException(new SecurityException($"Transport layer security protocols assigned as configured: \"{EnabledSslProtocols}\", however, event log entry for security exception could not be written: {ex.Message}", ex));
}
CheckCertificateRevocation = settings["CheckCertificateRevocation"].ValueAs(CheckCertificateRevocation);
CertificateFile = settings["CertificateFile"].ValueAs(m_certificateFile);
TrustedCertificatesPath = settings["TrustedCertificatesPath"].ValueAs(TrustedCertificatesPath);
ValidPolicyErrors = settings["ValidPolicyErrors"].ValueAs(ValidPolicyErrors);
ValidChainFlags = settings["ValidChainFlags"].ValueAs(ValidChainFlags);
PayloadAware = settings["PayloadAware"].ValueAs(PayloadAware);
IntegratedSecurity = settings["IntegratedSecurity"].ValueAs(IntegratedSecurity);
AllowDualStackSocket = settings["AllowDualStackSocket"].ValueAs(AllowDualStackSocket);
MaxSendQueueSize = settings["MaxSendQueueSize"].ValueAs(MaxSendQueueSize);
NoDelay = settings["NoDelay"].ValueAs(NoDelay);
}
if (!FilePath.InApplicationPath(TrustedCertificatesPath))
OnConnectionException(new SecurityException($"Trusted certificates path \"{TrustedCertificatesPath}\" is not in application path"));
}
///
/// Connects the to the server asynchronously.
///
/// Attempt is made to connect the when it is not disconnected.
/// for the asynchronous operation.
public override WaitHandle ConnectAsync()
{
ConnectState connectState = null;
// If the client is already connecting or connected, there is nothing to do
if (CurrentState != ClientState.Disconnected || m_disposed)
return m_connectWaitHandle;
try
{
// If we do not already have a wait handle to use
// for connections, get one from the base class
if (m_connectWaitHandle == null)
m_connectWaitHandle = (ManualResetEvent)base.ConnectAsync();
// Create state object for the asynchronous connection loop
connectState = new ConnectState();
// Store connectState in m_connectState so that calls to Disconnect
// and Dispose can dispose resources and cancel asynchronous loops
m_connectState = connectState;
// Prepare for connection attempt
OnConnectionAttempt();
m_connectWaitHandle.Reset();
// Overwrite setting from the config file if integrated security exists in connection string
if (m_connectData.TryGetValue("integratedSecurity", out string integratedSecuritySetting))
IntegratedSecurity = integratedSecuritySetting.ParseBoolean();
#if MONO
// Force integrated security to be False under Mono since it's not supported
IntegratedSecurity = false;
#endif
// Overwrite config file if no delay exists in connection string.
if (m_connectData.TryGetValue("noDelay", out string noDelaySetting))
NoDelay = noDelaySetting.ParseBoolean();
// Initialize state object for the asynchronous connection loop
Match endpoint = Regex.Match(ServerList[ServerIndex], Transport.EndpointFormatRegex);
connectState.ConnectArgs.RemoteEndPoint = Transport.CreateEndPoint(endpoint.Groups["host"].Value, int.Parse(endpoint.Groups["port"].Value), m_ipStack);
connectState.ConnectArgs.SocketFlags = SocketFlags.None;
connectState.ConnectArgs.UserToken = connectState;
connectState.ConnectArgs.Completed += (sender, args) => ProcessConnect((ConnectState)args.UserToken);
// Create client socket
connectState.Socket = Transport.CreateSocket(m_connectData["interface"], 0, ProtocolType.Tcp, m_ipStack, AllowDualStackSocket);
connectState.Socket.NoDelay = NoDelay;
// Initiate the asynchronous connection loop
ConnectAsync(connectState);
}
catch (Exception ex)
{
// Log exception during connection attempt
OnConnectionException(ex);
// Terminate the connection
if (connectState != null)
TerminateConnection(connectState.Token);
// Ensure that the wait handle is set so that operations waiting
// for completion of the asynchronous connection loop can continue
m_connectWaitHandle?.Set();
}
finally
{
// If the operation was cancelled during execution,
// make sure to dispose of erroneously allocated resources
if (connectState != null && connectState.Token.Cancelled)
connectState.Dispose();
}
// Return the wait handle that signals completion
// of the asynchronous connection loop
return m_connectWaitHandle;
}
///
/// Initiates an asynchronous connection attempt.
///
private void ConnectAsync(ConnectState connectState)
{
if (connectState.Token.Cancelled)
return;
if (!connectState.Socket.ConnectAsync(connectState.ConnectArgs))
ThreadPool.QueueUserWorkItem(state => ProcessConnect(connectState));
}
private void ProcessConnect(ConnectState connectState)
{
try
{
// Quit if this connection loop has been cancelled
if (connectState.Token.Cancelled)
return;
// Increment the number of connection attempts that
// have occurred in this asynchronous connection loop
connectState.ConnectionAttempts++;
// Check the SocketAsyncEventArgs for errors during the asynchronous connection attempt
if (connectState.ConnectArgs.SocketError != SocketError.Success)
throw new SocketException((int)connectState.ConnectArgs.SocketError);
// Set the size of the buffer used by the socket to store incoming data from the server
connectState.Socket.ReceiveBufferSize = ReceiveBufferSize;
// Create the SslStream object used to perform
// send and receive operations on the socket
connectState.NetworkStream = new NetworkStream(connectState.Socket, true);
connectState.SslStream = new SslStream(connectState.NetworkStream, false, RemoteCertificateValidationCallback ?? CertificateChecker.ValidateRemoteCertificate, LocalCertificateSelectionCallback);
// Load trusted certificates from
// the trusted certificates directory
LoadTrustedCertificates();
// Begin authentication with the TlsServer
Match endpoint = Regex.Match(ServerList[ServerIndex], Transport.EndpointFormatRegex);
if (!connectState.Token.Cancelled)
{
connectState.TimeoutToken = new Action(() =>
{
SocketException ex = new SocketException((int)SocketError.TimedOut);
OnConnectionException(ex);
TerminateConnection(connectState.Token);
connectState.Dispose();
}).DelayAndExecute(15000);
try
{
connectState.SslStream.BeginAuthenticateAsClient(endpoint.Groups["host"].Value, m_clientCertificates, m_enabledSslProtocols, CheckCertificateRevocation, ProcessTlsAuthentication, connectState);
}
catch
{
connectState.TimeoutToken.Cancel();
throw;
}
}
}
catch (SocketException ex)
{
// Log exception during connection attempt
OnConnectionException(ex);
// If the connection is refused by the server,
// keep trying until we reach our maximum connection attempts
if (ex.SocketErrorCode == SocketError.ConnectionRefused && (MaxConnectionAttempts == -1 || connectState.ConnectionAttempts < MaxConnectionAttempts))
{
try
{
ConnectAsync(connectState);
}
catch
{
TerminateConnection(connectState.Token);
}
}
else
{
// For any other socket exception,
// terminate the connection
TerminateConnection(connectState.Token);
}
}
catch (Exception ex)
{
// Log exception during connection attempt
OnConnectionException(ex);
// Terminate the connection
TerminateConnection(connectState.Token);
}
finally
{
// If the operation was cancelled during execution,
// make sure to dispose of erroneously allocated resources
if (connectState.Token.Cancelled)
connectState.Dispose();
}
}
///
/// Callback method for asynchronous authenticate operation.
///
private void ProcessTlsAuthentication(IAsyncResult asyncResult)
{
ConnectState connectState = null;
ReceiveState receiveState = null;
SendState sendState = null;
try
{
// Get the connect state from the async result
connectState = (ConnectState)asyncResult.AsyncState;
// Attempt to cancel the timeout operation
if (!connectState.TimeoutToken.Cancel())
return;
// Quit if this connection loop has been cancelled
if (connectState.Token.Cancelled)
return;
// Complete the operation to authenticate with the server
connectState.SslStream.EndAuthenticateAsClient(asyncResult);
// Ensure that this client is authenticated and encrypted
if (EnabledSslProtocols != SslProtocols.None)
{
if (!connectState.SslStream.IsAuthenticated)
throw new InvalidOperationException("Connection could not be established because we could not authenticate with the server.");
if (!connectState.SslStream.IsEncrypted)
throw new InvalidOperationException("Connection could not be established because the data stream is not encrypted.");
}
if (IntegratedSecurity)
{
#if !MONO
// Check the state of cancellation one more time before
// proceeding to the next step of the connection loop
if (connectState.Token.Cancelled)
return;
// Create the NegotiateStream to begin authentication of the user's Windows credentials
connectState.NegotiateStream = new NegotiateStream(connectState.SslStream, true);
connectState.TimeoutToken = new Action(() =>
{
SocketException ex = new SocketException((int)SocketError.TimedOut);
OnConnectionException(ex);
TerminateConnection(connectState.Token);
connectState.Dispose();
}).DelayAndExecute(15000);
try
{
connectState.NegotiateStream.BeginAuthenticateAsClient(NetworkCredential ?? (NetworkCredential)CredentialCache.DefaultCredentials, string.Empty, ProcessIntegratedSecurityAuthentication, connectState);
}
catch
{
connectState.TimeoutToken.Cancel();
throw;
}
#endif
}
else
{
// Initialize state object for the asynchronous send loop
sendState = new SendState
{
Socket = connectState.Socket,
NetworkStream = connectState.NetworkStream,
SslStream = connectState.SslStream,
Token = connectState.Token
};
// Store sendState in m_sendState so that calls to Disconnect
// and Dispose can dispose resources and cancel asynchronous loops
m_sendState = sendState;
// Check the state of cancellation one more time before
// proceeding to the next step of the connection loop
if (connectState.Token.Cancelled)
return;
// Notify of established connection
m_connectWaitHandle.Set();
OnConnectionEstablished();
// Initialize state object for the asynchronous receive loop
receiveState = new ReceiveState
{
Socket = connectState.Socket,
NetworkStream = connectState.NetworkStream,
SslStream = connectState.SslStream,
Buffer = new byte[m_payloadMarker.Length + Payload.LengthSegment],
Token = connectState.Token
};
// Store receiveState in m_receiveState so that calls to Disconnect
// and Dispose can dispose resources and cancel asynchronous loops
m_receiveState = receiveState;
// Start receiving data
if (PayloadAware)
ReceivePayloadAwareAsync(receiveState);
else
ReceivePayloadUnawareAsync(receiveState);
// Further socket interactions are handled through the SslStream
// object, so the SocketAsyncEventArgs is no longer needed
connectState.ConnectArgs.Dispose();
}
}
catch (SocketException ex)
{
// Log exception during connection attempt
OnConnectionException(ex);
// If connectState is null, we cannot proceed
if (connectState == null)
return;
// If the connection is refused by the server,
// keep trying until we reach our maximum connection attempts
if (ex.SocketErrorCode == SocketError.ConnectionRefused &&
(MaxConnectionAttempts == -1 || connectState.ConnectionAttempts < MaxConnectionAttempts))
{
try
{
ConnectAsync(connectState);
}
catch
{
TerminateConnection(connectState.Token);
}
}
else
{
// For any other socket exception,
// terminate the connection
TerminateConnection(connectState.Token);
}
}
catch (Exception ex)
{
// Log exception during connection attempt
string errorMessage = $"Unable to authenticate connection to server: {CertificateChecker.ReasonForFailure ?? ex.Message}";
OnConnectionException(new Exception(errorMessage, ex));
// Terminate the connection
if (connectState != null)
TerminateConnection(connectState.Token);
}
finally
{
// If the operation was cancelled during execution,
// make sure to dispose of erroneously allocated resources
if (connectState != null && connectState.Token.Cancelled)
connectState.Dispose();
if (receiveState != null && receiveState.Token.Cancelled)
receiveState.Dispose();
if (sendState != null && sendState.Token.Cancelled)
sendState.Dispose();
}
}
#if !MONO
private void ProcessIntegratedSecurityAuthentication(IAsyncResult asyncResult)
{
ConnectState connectState = null;
ReceiveState receiveState = null;
SendState sendState = null;
try
{
// Get the connect state from the async result
connectState = (ConnectState)asyncResult.AsyncState;
// Attempt to cancel the timeout operation
if (!connectState.TimeoutToken.Cancel())
return;
// Quit if this connection loop has been cancelled
if (connectState.Token.Cancelled)
return;
try
{
// Complete the operation to authenticate with the server
connectState.NegotiateStream.EndAuthenticateAsClient(asyncResult);
}
catch (InvalidCredentialException)
{
if (!IgnoreInvalidCredentials)
throw;
}
// Initialize state object for the asynchronous send loop
sendState = new SendState
{
Socket = connectState.Socket,
NetworkStream = connectState.NetworkStream,
SslStream = connectState.SslStream,
Token = connectState.Token
};
// Store sendState in m_sendState so that calls to Disconnect
// and Dispose can dispose resources and cancel asynchronous loops
m_sendState = sendState;
// Check the state of cancellation one more time before
// proceeding to the next step of the connection loop
if (connectState.Token.Cancelled)
return;
// Notify of established connection
// and begin receiving data.
m_connectWaitHandle.Set();
OnConnectionEstablished();
// Initialize state object for the asynchronous receive loop
receiveState = new ReceiveState
{
Token = connectState.Token,
Socket = connectState.Socket,
NetworkStream = connectState.NetworkStream,
SslStream = connectState.SslStream,
Buffer = new byte[ReceiveBufferSize]
};
// Store receiveState in m_receiveState so that calls to Disconnect
// and Dispose can dispose resources and cancel asynchronous loops
m_receiveState = receiveState;
// Start receiving data
if (PayloadAware)
ReceivePayloadAwareAsync(receiveState);
else
ReceivePayloadUnawareAsync(receiveState);
// Further socket interactions are handled through the SslStream
// object, so the SocketAsyncEventArgs is no longer needed
connectState.ConnectArgs.Dispose();
}
catch (SocketException ex)
{
// Log exception during connection attempt
OnConnectionException(ex);
// If connectState is null, we cannot proceed
if (connectState == null)
return;
// If the connection is refused by the server,
// keep trying until we reach our maximum connection attempts
if (ex.SocketErrorCode == SocketError.ConnectionRefused && (MaxConnectionAttempts == -1 || connectState.ConnectionAttempts < MaxConnectionAttempts))
{
try
{
ConnectAsync(connectState);
}
catch
{
TerminateConnection(connectState.Token);
}
}
else
{
// For any other socket exception,
// terminate the connection
TerminateConnection(connectState.Token);
}
}
catch (Exception ex)
{
// Log exception during connection attempt
string errorMessage = $"Unable to authenticate connection to server: {CertificateChecker.ReasonForFailure ?? ex.Message}";
OnConnectionException(new Exception(errorMessage, ex));
// Terminate the connection
if (connectState != null)
TerminateConnection(connectState.Token);
}
finally
{
if (connectState != null)
{
// If the operation was cancelled during execution,
// make sure to dispose of erroneously allocated resources;
// otherwise, dispose of the NegotiateStream which is only used for authentication
if (connectState.Token.Cancelled)
connectState.Dispose();
else
connectState.NegotiateStream.Dispose();
}
if (receiveState != null && receiveState.Token.Cancelled)
receiveState.Dispose();
if (sendState != null && sendState.Token.Cancelled)
sendState.Dispose();
}
}
#endif
///
/// Initiate method for asynchronous receive operation of payload data in "payload-aware" mode.
///
private void ReceivePayloadAwareAsync(ReceiveState receiveState)
{
if (receiveState.Token.Cancelled)
return;
int length = receiveState.PayloadLength < 0 ? m_payloadMarker.Length + Payload.LengthSegment : receiveState.PayloadLength;
receiveState.SslStream.BeginRead(receiveState.Buffer,
receiveState.Offset,
length - receiveState.Offset,
ProcessReceivePayloadAware,
receiveState);
}
///
/// Callback method for asynchronous receive operation of payload data in "payload-aware" mode.
///
private void ProcessReceivePayloadAware(IAsyncResult asyncResult)
{
ReceiveState receiveState = null;
try
{
// Get the receive state from the async result
receiveState = (ReceiveState)asyncResult.AsyncState;
// Quit if this receive loop has been cancelled
if (receiveState.Token.Cancelled)
return;
// Determine if the server disconnected gracefully
if (!receiveState.Socket.Connected)
throw new SocketException((int)SocketError.Disconnecting);
// Update statistics and bytes received
int bytesReceived = receiveState.SslStream.EndRead(asyncResult);
UpdateBytesReceived(bytesReceived);
receiveState.Offset += bytesReceived;
// Sanity check to determine if the server disconnected gracefully
if (bytesReceived == 0)
throw new SocketException((int)SocketError.Disconnecting);
if (receiveState.PayloadLength < 0)
{
// If we haven't parsed the length of the payload yet, attempt to parse it
receiveState.PayloadLength = Payload.ExtractLength(receiveState.Buffer, receiveState.Offset, m_payloadMarker, m_payloadEndianOrder);
if (receiveState.PayloadLength > 0)
{
receiveState.Offset = 0;
if (receiveState.Buffer.Length < receiveState.PayloadLength)
receiveState.Buffer = new byte[receiveState.PayloadLength];
}
}
else if (receiveState.Offset == receiveState.PayloadLength)
{
// We've received the entire payload so notify the user
OnReceiveDataComplete(receiveState.Buffer, receiveState.PayloadLength);
// Reset payload length
receiveState.Offset = 0;
receiveState.PayloadLength = -1;
}
// Continue asynchronous loop
ReceivePayloadAwareAsync(receiveState);
}
catch (ObjectDisposedException)
{
// Make sure connection is terminated when client is disposed
if (receiveState != null)
TerminateConnection(receiveState.Token);
}
catch (SocketException ex)
{
// Log exception during receive operation
OnReceiveDataException(ex);
// Terminate connection when socket exception is encountered
if (receiveState != null)
TerminateConnection(receiveState.Token);
}
catch (Exception ex)
{
try
{
// For any other exception, notify and resume
OnReceiveDataException(ex);
ReceivePayloadAwareAsync(receiveState);
}
catch
{
// Terminate connection if resume fails
if (receiveState != null)
TerminateConnection(receiveState.Token);
}
}
finally
{
// If the operation was cancelled during execution,
// make sure to dispose of allocated resources
if (receiveState != null && receiveState.Token.Cancelled)
receiveState.Dispose();
}
}
///
/// Initiate method for asynchronous receive operation of payload data in "payload-unaware" mode.
///
private void ReceivePayloadUnawareAsync(ReceiveState receiveState)
{
if (receiveState.Token.Cancelled)
return;
receiveState.SslStream.BeginRead(receiveState.Buffer,
0,
receiveState.Buffer.Length,
ProcessReceivePayloadUnaware,
receiveState);
}
///
/// Callback method for asynchronous receive operation of payload data in "payload-unaware" mode.
///
private void ProcessReceivePayloadUnaware(IAsyncResult asyncResult)
{
ReceiveState receiveState = null;
try
{
// Get the receive state from the async result
receiveState = (ReceiveState)asyncResult.AsyncState;
// Quit if this receive loop has been cancelled
if (receiveState.Token.Cancelled)
return;
// Determine if the server disconnected gracefully
if (!receiveState.Socket.Connected)
throw new SocketException((int)SocketError.Disconnecting);
// Update bytes received
int bytesReceived = receiveState.SslStream.EndRead(asyncResult);
UpdateBytesReceived(bytesReceived);
receiveState.PayloadLength = bytesReceived;
// Sanity check to determine if the server disconnected gracefully
if (bytesReceived == 0)
throw new SocketException((int)SocketError.Disconnecting);
// Notify of received data and resume the asynchronous loop
OnReceiveDataComplete(receiveState.Buffer, bytesReceived);
ReceivePayloadUnawareAsync(receiveState);
}
catch (ObjectDisposedException)
{
// Make sure connection is terminated when client is disposed
if (receiveState != null)
TerminateConnection(receiveState.Token);
}
catch (SocketException ex)
{
// Log exception during receive operation
OnReceiveDataException(ex);
// Terminate connection when socket exception is encountered
if (receiveState != null)
TerminateConnection(receiveState.Token);
}
catch (Exception ex)
{
try
{
// For any other exception, notify and resume
OnReceiveDataException(ex);
ReceivePayloadUnawareAsync(receiveState);
}
catch
{
// Terminate connection if resume fails
if (receiveState != null)
TerminateConnection(receiveState.Token);
}
}
finally
{
// If the operation was cancelled during execution,
// make sure to dispose of allocated resources
if (receiveState != null && receiveState.Token.Cancelled)
receiveState.Dispose();
}
}
///
/// When overridden in a derived class, reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset.
///
/// Destination buffer used to hold copied bytes.
/// 0-based starting index into destination to begin writing data.
/// The number of bytes to read from current received data buffer and write into .
/// The number of bytes read.
///
/// This function should only be called from within the event handler. Calling this method outside this event
/// will have unexpected results.
///
public override int Read(byte[] buffer, int startIndex, int length)
{
ReceiveState receiveState = m_receiveState;
if (receiveState == null || receiveState.Token.Cancelled)
return 0;
buffer.ValidateParameters(startIndex, length);
if (receiveState.Buffer == null)
throw new InvalidOperationException("No received data buffer has been defined to read.");
int sourceLength = receiveState.PayloadLength - ReadIndex;
int readBytes = length > sourceLength ? sourceLength : length;
Buffer.BlockCopy(receiveState.Buffer, ReadIndex, buffer, startIndex, readBytes);
// Update read index for next call
ReadIndex += readBytes;
if (ReadIndex >= receiveState.PayloadLength)
ReadIndex = 0;
return readBytes;
}
///
/// Requests that the client attempt to move to the next .
///
/// true if request succeeded; otherwise, false.
///
/// Return value will only be true if changed.
///
public override bool RequestNextServerIndex()
{
int serverListLength = ServerList.Length;
if (serverListLength < 2)
return false;
// When multiple servers are available, move to next server connection
ServerIndex++;
if (ServerIndex >= serverListLength)
ServerIndex = 0;
return true;
}
///
/// When overridden in a derived class, sends data to the server asynchronously.
///
/// The buffer that contains the binary data to be sent.
/// The zero-based position in the at which to begin sending data.
/// The number of bytes to be sent from starting at the .
/// for the asynchronous operation.
protected override WaitHandle SendDataAsync(byte[] data, int offset, int length)
{
SendState sendState = null;
try
{
// Get the current send state
sendState = m_sendState;
// Quit if the send loop has been cancelled
if (sendState.Token.Cancelled)
return null;
// Prepare for payload-aware transmission
if (PayloadAware)
Payload.AddHeader(ref data, ref offset, ref length, m_payloadMarker, m_payloadEndianOrder);
// Create payload and wait handle.
TlsClientPayload payload = FastObjectFactory.CreateObjectFunction();
ManualResetEvent handle = new ManualResetEvent(false);
payload.Data = data;
payload.Offset = offset;
payload.Length = length;
payload.WaitHandle = handle;
// Execute operation to take action if the client
// has reached the maximum send queue size
m_dumpPayloadsOperation.TryRun();
// Queue payload for sending
sendState.SendQueue.Enqueue(payload);
// If the send loop is not already running, start the send loop
if (!sendState.Token.Cancelled)
{
if (Interlocked.CompareExchange(ref sendState.Sending, 1, 0) == 0)
SendPayloadAsync(sendState);
// Notify that the send operation has started.
OnSendDataStart();
// Return the async handle that can be used to wait for the async operation to complete
return handle;
}
}
catch (Exception ex)
{
// Log exception during send operation
OnSendDataException(ex);
}
finally
{
// If the operation was cancelled during execution,
// make sure to dispose of allocated resources
if (sendState != null && sendState.Token.Cancelled)
sendState.Dispose();
}
return null;
}
///
/// Sends a payload on the socket.
///
private void SendPayloadAsync(SendState sendState)
{
try
{
// Quit if this send loop has been cancelled
if (sendState.Token.Cancelled)
return;
if (sendState.SendQueue.TryDequeue(out TlsClientPayload payload))
{
// Save the payload currently
// being sent to the send state
sendState.Payload = payload;
byte[] data = payload.Data;
int offset = payload.Offset;
int length = payload.Length;
// Send payload to the client asynchronously.
sendState.SslStream.BeginWrite(data, offset, length, ProcessSend, sendState);
}
else
{
// No more payloads to send, so stop sending payloads
Interlocked.Exchange(ref sendState.Sending, 0);
// Double-check to ensure that a new payload didn't appear before exiting the send loop
if (!sendState.SendQueue.IsEmpty && Interlocked.CompareExchange(ref sendState.Sending, 1, 0) == 0)
ThreadPool.QueueUserWorkItem(state => SendPayloadAsync((SendState)state), sendState);
}
}
catch (Exception ex)
{
// Log exception during send operation
OnSendDataException(ex);
// Continue asynchronous send loop
ThreadPool.QueueUserWorkItem(state => SendPayloadAsync((SendState)state), sendState);
}
finally
{
// If the operation was cancelled during execution,
// make sure to dispose of allocated resources
if (sendState.Token.Cancelled)
sendState.Dispose();
}
}
///
/// Callback method for asynchronous send operation.
///
private void ProcessSend(IAsyncResult asyncResult)
{
SendState sendState = null;
ManualResetEvent handle = null;
try
{
// Get the send state from the async result
sendState = (SendState)asyncResult.AsyncState;
// Get the current payload and its wait handle
TlsClientPayload payload = sendState.Payload;
handle = payload.WaitHandle;
// Quit if this send loop has been cancelled
if (sendState.Token.Cancelled)
return;
// Determine if the server disconnected gracefully
if (!sendState.Socket.Connected)
throw new SocketException((int)SocketError.Disconnecting);
// Complete the send operation
sendState.SslStream.EndWrite(asyncResult);
try
{
// Set the wait handle to indicate
// the send operation has finished
handle.Set();
}
catch (ObjectDisposedException)
{
// Ignore if the consumer has
// disposed of the wait handle
}
// Notify that the send operation is complete
UpdateBytesSent(payload.Length);
OnSendDataComplete();
}
catch (ObjectDisposedException)
{
// Make sure connection is terminated when client is disposed
if (sendState != null)
TerminateConnection(sendState.Token);
}
catch (SocketException ex)
{
// Log exception during send operation
OnSendDataException(ex);
// Terminate connection when socket exception is encountered
if (sendState != null)
TerminateConnection(sendState.Token);
}
catch (Exception ex)
{
// For any other exception, notify and resume
OnSendDataException(ex);
}
finally
{
// If the operation was cancelled during execution,
// make sure to dispose of allocated resources
if (sendState != null && sendState.Token.Cancelled)
sendState.Dispose();
try
{
// Make sure to set the wait handle
// even if an exception occurs
handle?.Set();
}
catch (ObjectDisposedException)
{
// Ignore if the consumer has
// disposed of the wait handle
}
// Attempt to send the next payload
SendPayloadAsync(sendState);
}
}
///
/// When overridden in a derived class, disconnects client from the server synchronously.
///
public override void Disconnect()
{
try
{
if (CurrentState == ClientState.Disconnected)
return;
ConnectState connectState = m_connectState;
ReceiveState receiveState = m_receiveState;
SendState sendState = m_sendState;
if (connectState != null)
{
TerminateConnection(connectState.Token);
connectState.Socket.Disconnect(false);
connectState.Dispose();
}
receiveState?.Dispose();
sendState?.Dispose();
m_connectWaitHandle?.Set();
}
catch (ObjectDisposedException)
{
// This can be safely ignored
}
catch (Exception ex)
{
OnSendDataException(new InvalidOperationException($"Disconnect exception: {ex.Message}", ex));
}
}
///
/// Releases the unmanaged resources used by the and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (m_disposed)
return;
try
{
if (!disposing)
return;
if (m_connectState != null)
{
TerminateConnection(m_connectState.Token);
m_connectState.Dispose();
m_connectState = null;
}
if (m_receiveState != null)
{
m_receiveState.Dispose();
m_receiveState = null;
}
if (m_sendState != null)
{
m_sendState.Dispose();
m_sendState = null;
}
if (m_connectWaitHandle != null)
{
m_connectWaitHandle.Set();
m_connectWaitHandle.Dispose();
m_connectWaitHandle = null;
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
///
/// When overridden in a derived class, validates the specified .
///
/// The connection string to be validated.
protected override void ValidateConnectionString(string connectionString)
{
m_connectData = connectionString.ParseKeyValuePairs();
// Derive desired IP stack based on specified "interface" setting, adding setting if it's not defined
m_ipStack = Transport.GetInterfaceIPStack(m_connectData);
// Check if 'server' property is missing.
if (!m_connectData.ContainsKey("server") || string.IsNullOrWhiteSpace(m_connectData["server"]))
throw new ArgumentException($"Server property is missing (Example: {DefaultConnectionString})");
// Backwards compatibility adjustments.
// New Format: Server=localhost:8888
// Old Format: Server=localhost; Port=8888
if (m_connectData.ContainsKey("port") && !m_connectData["server"].Contains(','))
m_connectData["server"] = $"{m_connectData["server"]}:{m_connectData["port"]}";
m_serverList = null;
foreach (string server in ServerList)
{
// Check if 'server' property is valid.
Match endpoint = Regex.Match(server, Transport.EndpointFormatRegex);
if (endpoint == Match.Empty)
throw new FormatException($"Server property is invalid (Example: {DefaultConnectionString})");
if (!Transport.IsPortNumberValid(endpoint.Groups["port"].Value))
throw new ArgumentOutOfRangeException(nameof(connectionString), $"Server port must between {Transport.PortRangeLow} and {Transport.PortRangeHigh}");
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected override void OnSendDataException(Exception ex)
{
if (CurrentState != ClientState.Disconnected)
base.OnSendDataException(ex);
else
Logger.SwallowException(ex, "TlsClient.cs: The client state was disconnected");
}
///
/// Raises the event.
///
/// Exception to send to event.
protected void OnReceiveDataException(SocketException ex)
{
if (ex.SocketErrorCode != SocketError.Disconnecting)
OnReceiveDataException((Exception)ex);
else
Logger.SwallowException(ex, "TLsClient.cs: The socket was disconnecting");
}
///
/// Raises the event.
///
/// Exception to send to event.
protected override void OnReceiveDataException(Exception ex)
{
if (CurrentState != ClientState.Disconnected)
base.OnReceiveDataException(ex);
else
Logger.SwallowException(ex, "TlsClient.cs: The socket was disconnected");
}
///
/// Dumps payloads from the send queue when the send queue grows too large.
///
private void DumpPayloads()
{
SendState sendState = m_sendState;
// Quit if this send loop has been cancelled
if (sendState == null || sendState.Token.Cancelled)
return;
// Check to see if the client has reached the maximum send queue size.
if (MaxSendQueueSize <= 0 || sendState.SendQueue.Count < MaxSendQueueSize)
return;
for (int i = 0; i < MaxSendQueueSize; i++)
{
if (sendState.Token.Cancelled)
return;
if (sendState.SendQueue.TryDequeue(out TlsClientPayload payload))
{
payload.WaitHandle.Set();
payload.WaitHandle.Dispose();
}
}
throw new InvalidOperationException($"TLS client reached maximum send queue size. {MaxSendQueueSize} payloads dumped from the queue.");
}
///
/// Processes the termination of client.
///
private void TerminateConnection(CancellationToken cancellationToken)
{
try
{
// Cancel all asynchronous loops associated with the cancellation token and notify user
// of terminated connection if the connection had not previously been terminated
if (!cancellationToken.Cancel())
OnConnectionTerminated();
}
catch (ThreadAbortException)
{
// This is a normal exception
throw;
}
catch
{
// Other exceptions can happen (e.g., NullReferenceException) if thread
// resumes and the class is disposed middle way through this method
}
}
///
/// Returns the certificate set by the user.
///
private X509Certificate DefaultLocalCertificateSelectionCallback(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
{
return Certificate;
}
///
/// Loads the list of trusted certificates into the default certificate checker.
///
private void LoadTrustedCertificates()
{
if (RemoteCertificateValidationCallback != null || m_certificateChecker != null)
return;
m_defaultCertificateChecker.TrustedCertificates.Clear();
string trustedCertificatesPath = FilePath.AddPathSuffix(FilePath.GetAbsolutePath(TrustedCertificatesPath));
foreach (string fileName in FilePath.GetFileList(trustedCertificatesPath))
m_defaultCertificateChecker.TrustedCertificates.Add(new X509Certificate2(fileName));
}
#endregion
}
}