using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Net; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Discovery; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using ArchestrAServices.Common; using ArchestrAServices.Proxy; using Asb.Base.V2.ContractTypes; namespace Asb.Base.V2; public class BaseV2Client : IBaseV2, IDisposable where T : class, IAuthenticateAsb { private readonly object connectionLock = new object(); private readonly string connectionUser = string.Empty; private readonly string connectionApplication = string.Empty; private readonly TimeSpan timeToWaitForConnection = DefaultTimeToWaitForConnection; private IClientManagement clientManager; private long connectionInProgress; private ConnectContext successfulConnectionResult; private ManualResetEvent connectionEstablishedEvent; private bool disposed; public static TimeSpan DefaultTimeToWaitForConnection => TimeSpan.FromSeconds(20.0); public SecureCommunicationModes SecureCommunicationMode { get; set; } public DateTime ServiceLoadTime { get; set; } public bool Connected { get { if (successfulConnectionResult?.ServiceChannel != null) { return successfulConnectionResult.ServiceChannel.State == CommunicationState.Opened; } return false; } } public CommunicationState State { get { if (successfulConnectionResult != null && successfulConnectionResult.ServiceChannel != null) { return successfulConnectionResult.ServiceChannel.State; } return CommunicationState.Closed; } } public ConnectionState DownstreamConnectionState { get; protected set; } internal ConnectContext Context => successfulConnectionResult; protected T ChannelClient => successfulConnectionResult.ServiceClient; public Guid ConnectionId { get { if (successfulConnectionResult != null) { return successfulConnectionResult.ConnectionId; } return Guid.Empty; } set { } } public BaseV2Client() { clientManager = new ClientManagement(); SecureCommunicationMode = RegistryHandler.SecureCommunicationMode; } public BaseV2Client(TimeSpan connectTimeout) : this() { if (connectTimeout.TotalMilliseconds > 2147483647.0 || connectTimeout < TimeSpan.Zero) { throw new ArgumentOutOfRangeException("connectTimeout"); } timeToWaitForConnection = connectTimeout; } internal BaseV2Client(TimeSpan connectTimeout, IClientManagement manager) : this(manager) { if (connectTimeout.TotalMilliseconds > 2147483647.0 || connectTimeout < TimeSpan.Zero) { throw new ArgumentOutOfRangeException("connectTimeout"); } timeToWaitForConnection = connectTimeout; } internal BaseV2Client(TimeSpan connectTimeout, IClientManagement manager, string application, string user) : this(connectTimeout, manager) { connectionApplication = application; connectionUser = user; } public BaseV2Client(TimeSpan connectTimeout, string application, string user) : this(connectTimeout) { connectionApplication = application; connectionUser = user; } internal BaseV2Client(IClientManagement manager) { clientManager = manager; SecureCommunicationMode = RegistryHandler.SecureCommunicationMode; } public bool Connect(EndpointDiscoveryMetadata selectedMetadata, string solutionName, ClientAccess access, Action errorCallback) { return Connect(selectedMetadata, solutionName, access, errorCallback, useSecureConnection: false); } public bool Connect(EndpointDiscoveryMetadata selectedMetadata, string solutionName, ClientAccess access, Action errorCallback, bool useSecureConnection) { ServiceTrace.LogCsv("Connect (endpoint discovered) entry"); ServiceTrace.LogResume("Connect (endpoint discovered) entry"); if (Interlocked.CompareExchange(ref connectionInProgress, 1L, 0L) != 0L) { string text = string.Format(CultureInfo.InvariantCulture, "Connect: another connection is in progress, cannot attempt a connection using selected discovery metadata"); ServiceTrace.LogError(text); errorCallback?.Invoke(text); return false; } try { bool flag = EstablishConnection(selectedMetadata, solutionName, access, errorCallback, useSecureConnection); ServiceTrace.LogSuspend("Connect (endpoint discovered) exit, {0}", flag ? "succeeded" : "failed"); ServiceTrace.LogCsv("Connect (endpoint discovered) exit [success]", flag); return flag; } finally { Interlocked.Exchange(ref connectionInProgress, 0L); } } public void Disconnect() { ServiceTrace.LogCsv("Disconnect entry"); ServiceTrace.LogResume("Disconnect entry"); if (successfulConnectionResult == null || !successfulConnectionResult.Success) { return; } SystemAuthenticationClientAuthentication.DisconnectSecureSession(successfulConnectionResult.ConnectionId, delegate(DisconnectRequest request) { if (successfulConnectionResult != null && successfulConnectionResult.Success) { if (successfulConnectionResult.ServiceClient != null && successfulConnectionResult.ServiceChannel != null && successfulConnectionResult.ServiceChannel.State != CommunicationState.Faulted) { ServiceTrace.LogCsv("Calling WCF channel Disconnect method"); successfulConnectionResult.ServiceClient.Disconnect(request); ServiceTrace.LogCsv("Returned from WCF channel Disconnect method"); } Reset(); } }); DownstreamConnectionState = ConnectionState.Unknown; ServiceTrace.LogSuspend("Disconnect exit"); ServiceTrace.LogCsv("Disconnect exit"); } public void Abort() { Reset(); } internal void InjectClientManager(IClientManagement manager) { clientManager = manager; } protected static ReadOnlyCollection DiscoverEndpoints(XmlQualifiedName contract, string scopeRule) { return DiscoverEndpoints(contract, scopeRule, null); } protected static ReadOnlyCollection DiscoverEndpoints(XmlQualifiedName contract, string scopeRule, bool? useSecureConnection) { List list = new ClientManagement().DiscoverEndpointsForContract(contract, scopeRule)?.ToList(); if (list == null) { ServiceTrace.LogWarning(string.Format(CultureInfo.InvariantCulture, "DiscoverEndpoints: null FindResponse finding contract {0} with access name {1}", new object[2] { contract.Name, scopeRule })); return new ReadOnlyCollection(new EndpointDiscoveryMetadata[0]); } if (list.Count == 0) { ServiceTrace.LogInfo(string.Format(CultureInfo.InvariantCulture, "DiscoverEndpoints found no endpoints for contract {0} with access name {1}", new object[2] { contract.Name, scopeRule })); return new ReadOnlyCollection(new EndpointDiscoveryMetadata[0]); } return new ReadOnlyCollection(list); } protected bool Connect(XmlQualifiedName contract, string scopeRule, ClientAccess access, Action errorCallback) { return Connect(contract, scopeRule, access, errorCallback, useSecureConnection: false); } protected bool Connect(XmlQualifiedName contract, string scopeRule, ClientAccess access, Action errorCallback, bool useSecureConnection) { ServiceTrace.LogCsv("Connect (discover endpoint) entry"); ServiceTrace.LogResume("Connect (discover endpoint) entry"); if (contract == null) { string text = string.Format(CultureInfo.InvariantCulture, "Connect: no contract provided, no connection is possible"); ServiceTrace.LogError(text); errorCallback?.Invoke(text); return false; } if (Interlocked.CompareExchange(ref connectionInProgress, 1L, 0L) != 0L) { string text2 = string.Format(CultureInfo.InvariantCulture, "Connect: another connection is in progress, cannot attempt a connection with a specified contract and access name"); ServiceTrace.LogError(text2); errorCallback?.Invoke(text2); return false; } try { List list = clientManager.DiscoverEndpointsForContract(contract, scopeRule)?.ToList(); if (list == null) { string text3 = string.Format(CultureInfo.InvariantCulture, "Connect: null FindResponse finding contract {0} with access name {1}", new object[2] { contract.Name, scopeRule }); ServiceTrace.LogError(text3); errorCallback?.Invoke(text3); return false; } if (list.Count == 0) { string text4 = string.Format(CultureInfo.InvariantCulture, "Connect found no endpoints for contract {0} with access name {1}", new object[2] { contract.Name, scopeRule }); ServiceTrace.LogError(text4); errorCallback?.Invoke(text4); return false; } EndpointDiscoveryMetadata selectedMetadata; if (list.Count > 1) { Random random = new Random(DateTime.Now.Millisecond); selectedMetadata = list[random.Next(list.Count)]; } else { selectedMetadata = list[0]; } bool flag = EstablishConnection(selectedMetadata, string.Empty, access, errorCallback, useSecureConnection); ServiceTrace.LogSuspend("Connect (discover endpoint) exit, {0}", flag ? "succeeded" : "failed"); ServiceTrace.LogCsv("Connect (discover endpoint) exit [success]", flag); return flag; } finally { Interlocked.Exchange(ref connectionInProgress, 0L); } } protected UserTokenContract EncryptUserToken(UserToken originalToken) { if (originalToken == null) { return null; } UserTokenContract userTokenContract = new UserTokenContract { Encryption = originalToken.Encryption, HostName = originalToken.HostName, IdType = originalToken.IdType, LocationId = originalToken.LocationId, UserName = originalToken.UserName, Validity = originalToken.Validity }; SystemAuthenticationClientAuthentication clientAuthenticator = SysAuthenticatorClientCache.GetClientAuthenticator(successfulConnectionResult.ConnectionId); if (clientAuthenticator == null || !clientAuthenticator.SecureSessionEstablished) { throw new InvalidOperationException("Cannot encrypt user token due to session in wrong state"); } if (originalToken.Password != null) { byte[] bytes = Encoding.UTF8.GetBytes(originalToken.Password); userTokenContract.Password = clientAuthenticator.Encypher(bytes, clientAuthenticator.EncryptionKey); } if (originalToken.SamlToken != null) { userTokenContract.SamlToken = clientAuthenticator.Encypher(originalToken.SamlToken, clientAuthenticator.EncryptionKey); } if (originalToken.JwtToken != null) { byte[] bytes2 = Encoding.UTF8.GetBytes(originalToken.JwtToken); userTokenContract.JwtToken = clientAuthenticator.Encypher(bytes2, clientAuthenticator.EncryptionKey); } if (originalToken.X509Certificate != null) { userTokenContract.X509Certificate = clientAuthenticator.Encypher(originalToken.X509Certificate, clientAuthenticator.EncryptionKey); } return userTokenContract; } protected void Reset() { successfulConnectionResult?.Dispose(); successfulConnectionResult = null; } private bool EstablishConnection(EndpointDiscoveryMetadata selectedMetadata, string solutionName, ClientAccess access, Action errorCallback, bool useSecureConnection) { ServiceTrace.LogCsv("CreateConnectContext entry"); ServiceTrace.LogResume("CreateConnectContext entry"); if (selectedMetadata == null) { errorCallback?.Invoke("No discovery endpoint metadata provided, cannot connect"); return false; } if (string.IsNullOrWhiteSpace(solutionName)) { solutionName = new ASBSolutionManager().GetASBSolutionName(selectedMetadata, out var errorMessage); if (!string.IsNullOrEmpty(errorMessage)) { errorCallback?.Invoke(errorMessage); return false; } } lock (connectionLock) { successfulConnectionResult = null; connectionEstablishedEvent = new ManualResetEvent(initialState: false); } using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource()) { SpinConnectionTasks(selectedMetadata.ListenUris, solutionName, access, errorCallback, useSecureConnection, cancellationTokenSource.Token); bool flag = connectionEstablishedEvent.WaitOne(timeToWaitForConnection); ServiceTrace.LogVerbose("{0} task connected with the service", flag ? "A" : "No"); cancellationTokenSource.Cancel(); } bool result; lock (connectionLock) { connectionEstablishedEvent.Dispose(); connectionEstablishedEvent = null; result = true; if (successfulConnectionResult == null) { result = false; errorCallback?.Invoke("No connection was established with a service endpoint"); } } ServiceTrace.LogSuspend("CreateConnectContext exit"); ServiceTrace.LogCsv("CreateConnectContext exit"); return result; } private void SpinConnectionTasks(Collection listenUris, string solutionName, ClientAccess access, Action errorCallback, bool useSecureConnection, CancellationToken cancellationToken) { ServiceTrace.LogVerbose("Spinning tasks for {0} listen Uris", listenUris.Count); foreach (Uri endpointUri in listenUris) { string selectedEndpointUri = endpointUri.ToString(); Task.Factory.StartNew(delegate { NetTcpBindingSecurityMode securityMode = (useSecureConnection ? NetTcpBindingSecurityMode.CertificateEncryption : NetTcpBindingSecurityMode.None); Binding binding = SvcUtilities.GetBinding(selectedEndpointUri, securityMode); binding.OpenTimeout = new TimeSpan(0, 10, 0); binding.ReceiveTimeout = new TimeSpan(0, 10, 0); binding.SendTimeout = new TimeSpan(0, 10, 0); binding.CloseTimeout = new TimeSpan(0, 10, 0); EndpointAddress serviceEndpointAddress = new EndpointAddress(selectedEndpointUri); if (useSecureConnection) { IPHostEntry hostEntry = Dns.GetHostEntry(endpointUri.Host); serviceEndpointAddress = new EndpointAddress(endpointUri, EndpointIdentity.CreateDnsIdentity(hostEntry.HostName)); } InternalConnect(serviceEndpointAddress, binding, solutionName, access, errorCallback, cancellationToken); }, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); } } private void InternalConnect(EndpointAddress serviceEndpointAddress, Binding binding, string solutionName, ClientAccess access, Action errorCallback, CancellationToken cancellationToken) { if (serviceEndpointAddress == null) { throw new ArgumentNullException("serviceEndpointAddress"); } if (binding == null) { throw new ArgumentNullException("binding"); } if (string.IsNullOrEmpty(solutionName)) { throw new ArgumentNullException("solutionName"); } try { EstablishChannelConnection(serviceEndpointAddress, binding, solutionName, access, errorCallback, cancellationToken); } catch (CommunicationException ex) { ServiceTrace.LogWarning("BaseV2Client caught CommunicationException opening channel: " + ex.Message); errorCallback?.Invoke("BaseV2Client caught CommunicationException opening channel: " + ex.Message); if (ex.InnerException != null) { ServiceTrace.LogWarning(" " + ex.InnerException.Message); errorCallback?.Invoke(" " + ex.InnerException.Message); } } catch (TimeoutException ex2) { ServiceTrace.LogWarning("BaseV2Client caught TimeoutException opening channel: " + ex2.Message); errorCallback?.Invoke("BaseV2Client caught TimeoutException opening channel: " + ex2.Message); if (ex2.InnerException != null) { ServiceTrace.LogWarning(" " + ex2.InnerException.Message); errorCallback?.Invoke(" " + ex2.InnerException.Message); } } catch (Exception ex3) { ServiceTrace.LogWarning("BaseV2Client caught exception opening channel: " + ex3.Message); errorCallback?.Invoke("BaseV2Client caught exception opening channel: " + ex3.Message); } } private void EstablishChannelConnection(EndpointAddress serviceEndpointAddress, Binding binding, string solutionName, ClientAccess access, Action errorCallback, CancellationToken cancellationToken) { ConnectContext connectContext = clientManager.CreateConnectContext(serviceEndpointAddress, binding, connectionApplication, connectionUser, cancellationToken); if ((connectContext != null && connectContext.ServiceChannelFactory?.State == CommunicationState.Closed) || (connectContext != null && connectContext.ServiceChannel?.State == CommunicationState.Closed)) { return; } if (clientManager.EstablishSecureSession(connectContext, solutionName, access)) { ServiceTrace.LogVerbose("Secure connection with endpoint {0} is established using solution {1}", serviceEndpointAddress.Uri.AbsoluteUri, solutionName); bool flag = false; lock (connectionLock) { if (successfulConnectionResult == null) { flag = true; successfulConnectionResult = connectContext; ServiceTrace.LogVerbose("Secure connection with endpoint {0} using solution {1} is first, signal success", serviceEndpointAddress.Uri.AbsoluteUri, solutionName); connectionEstablishedEvent?.Set(); } } if (!flag) { ServiceTrace.LogVerbose("Secure connection with endpoint {0} using solution {1} is NOT first, disconnect", serviceEndpointAddress.Uri.AbsoluteUri, solutionName); clientManager.DisconnectSecureSession(connectContext); connectContext.Dispose(); } } else { if (connectContext != null && !string.IsNullOrEmpty(connectContext.ErrorMessage)) { errorCallback?.Invoke(connectContext.ErrorMessage); } ServiceTrace.LogVerbose("Connection with endpoint {0} using solution {1} rejected", serviceEndpointAddress.Uri.AbsoluteUri, solutionName); if (connectionEstablishedEvent != null) { connectionEstablishedEvent.Set(); } } } public void OnConnect(ulong timeout, ConsumerMetadata metadata) { ServiceTrace.LogCsv("OnConnect entry"); ServiceTrace.LogResume("OnConnect entry"); throw new NotImplementedException(); } public ArchestrAResult KeepAlive() { ServiceTrace.LogCsv("KeepAlive entry"); ServiceTrace.LogResume("KeepAlive entry"); ArchestrAResult archestrAResult = PrepareToSend(new KeepAliveRequest(), delegate(ConnectedRequest request, T channelClient) { ServiceTrace.LogVerbose("Calling WCF channel KeepAlive method"); KeepAliveResponse keepAliveResponse = channelClient.KeepAlive((KeepAliveRequest)request); ServiceTrace.LogVerbose("Returned from WCF channel KeepAlive method"); if (keepAliveResponse == null) { return ArchestrAResult.MakeResult(ArchestrAError.BadNoCommunication, 0).AddErrorMessage("No response from the service"); } UpdateDownstreamConnectionState(keepAliveResponse); return keepAliveResponse.Result; }); ServiceTrace.LogSuspend("KeepAlive exit with {0}", archestrAResult.Success ? "success" : "failure"); ServiceTrace.LogCsv("KeepAlive exit [success]", archestrAResult.Success); return archestrAResult; } public ArchestrAResult GetStatusItems(out string[] items) { ServiceTrace.LogCsv("GetStatusItems entry"); ServiceTrace.LogResume("GetStatusItems entry"); string[] receivedItems = null; ArchestrAResult archestrAResult = PrepareToSend(new GetStatusItemsRequest(), delegate(ConnectedRequest request, T channelClient) { ServiceTrace.LogCsv("Calling WCF channel GetStatusItems method"); GetStatusItemsResponse statusItems = channelClient.GetStatusItems((GetStatusItemsRequest)request); ServiceTrace.LogCsv("Returned from WCF channel GetStatusItems method"); if (statusItems == null) { return ArchestrAResult.MakeResult(ArchestrAError.BadNoCommunication, 0).AddErrorMessage("No response from the service"); } UpdateDownstreamConnectionState(statusItems); receivedItems = statusItems.Items; return statusItems.Result; }); ServiceTrace.LogSuspend("GetStatusItems exit with {0}", archestrAResult.Success ? "success" : "failure"); ServiceTrace.LogCsv("GetStatusItems exit [success]", archestrAResult.Success); items = receivedItems; return archestrAResult; } public ArchestrAResult GetStatus(string[] itemsToReturn, out NamedValue[] items) { ServiceTrace.LogCsv("GetStatus entry"); ServiceTrace.LogResume("GetStatus entry"); NamedValue[] receivedItems = null; ArchestrAResult archestrAResult = PrepareToSend(new GetStatusRequest { ItemsToReturn = itemsToReturn }, delegate(ConnectedRequest request, T channelClient) { ServiceTrace.LogCsv("Calling WCF channel GetStatus method"); GetStatusResponse status = channelClient.GetStatus((GetStatusRequest)request); ServiceTrace.LogCsv("Returned from WCF channel GetStatus method"); if (status == null) { return ArchestrAResult.MakeResult(ArchestrAError.BadNoCommunication, 0).AddErrorMessage("No response from the service"); } UpdateDownstreamConnectionState(status); receivedItems = status.Items; return status.Result; }); ServiceTrace.LogSuspend("GetStatus exit with {0}", archestrAResult.Success ? "success" : "failure"); ServiceTrace.LogCsv("GetStatus exit [success]", archestrAResult.Success); items = receivedItems; return archestrAResult; } public void OnDisconnect() { ServiceTrace.LogCsv("OnDisconnect entry"); ServiceTrace.LogResume("OnDisconnect entry"); throw new NotImplementedException(); } protected ArchestrAResult PrepareToSend(ConnectedRequest request, Func processFunc) { if (request == null) { return ArchestrAResult.MakeResult(ArchestrAError.BadNoCommunication, 0).AddErrorMessage("No request message provided for signature"); } if (processFunc == null) { return ArchestrAResult.MakeResult(ArchestrAError.BadNoCommunication, 0).AddErrorMessage("No processing function provided"); } ArchestrAResult archestrAResult = SignRequest(request); if (!archestrAResult.Success) { return archestrAResult; } T channelClient = ChannelClient; if (channelClient == null) { return ArchestrAResult.MakeResult(ArchestrAError.BadNoCommunication, 0).AddErrorMessage("No connected client for the current connection Id"); } return processFunc(request, channelClient); } protected ArchestrAResult SignRequest(ConnectedRequest request) { ArchestrAResult result = ArchestrAResult.MakeResult(ArchestrAError.InvalidConnectionId, 0); if (successfulConnectionResult != null && successfulConnectionResult.ServiceClient != null && State != CommunicationState.Faulted) { SystemAuthenticationClientAuthentication clientAuthenticator = SysAuthenticatorClientCache.GetClientAuthenticator(successfulConnectionResult.ConnectionId); if (clientAuthenticator != null) { clientAuthenticator.Sign(request, forceHmac: false); return ArchestrAResult.MakeGoodResult(); } result.AddErrorMessage("Unable to find a signer using the current connection Id"); } else { result.AddErrorMessage("Unable to use the client proxy for the current connection Id"); } return result; } protected void UpdateDownstreamConnectionState(ConnectedResponse response) { if (response != null) { if (response.DownstreamConnectionState <= 6) { DownstreamConnectionState = (ConnectionState)response.DownstreamConnectionState; return; } ServiceTrace.LogWarning(string.Format(CultureInfo.InvariantCulture, "ASB Base V2 client failed to parse downstream connection state value of {0}", new object[1] { response.DownstreamConnectionState })); DownstreamConnectionState = ConnectionState.Unknown; } } ~BaseV2Client() { Dispose(disposing: false); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposed) { return; } if (disposing) { if (successfulConnectionResult != null) { successfulConnectionResult.Dispose(); } successfulConnectionResult = null; } disposed = true; Reset(); } }