/// <summary>
        /// This Service returns the Endpoints supported by a Server and all of the configuration information required to establish a SecureChannel and a Session.
        /// </summary>
        /// <param name="request">a request.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public static async Task<GetEndpointsResponse> GetEndpointsAsync(GetEndpointsRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            var client = new UaTcpDiscoveryClient(
                new EndpointDescription
                {
                    EndpointUrl = request.EndpointUrl,
                    SecurityMode = MessageSecurityMode.None,
                    SecurityPolicyUri = SecurityPolicyUris.None
                });
            try
            {
                await client.OpenAsync().ConfigureAwait(false);
                var response = await client.innerChannel.RequestAsync(request).ConfigureAwait(false);
                await client.CloseAsync().ConfigureAwait(false);
                return (GetEndpointsResponse)response;
            }
            catch (Exception)
            {
                await client.AbortAsync().ConfigureAwait(false);
                throw;
            }
        }
        public async Task ConnnectToAllEndpoints()
        {
            // get or add application certificate.
            var localCertificate = this.localDescription.GetCertificate();
            if (localCertificate == null)
            {
                throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Application certificate is missing.");
            }

            // discover available endpoints of server.
            var getEndpointsRequest = new GetEndpointsRequest
            {
                EndpointUrl = this.endpointUrl,
                ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
            };
            Console.WriteLine($"Discovering endpoints of '{getEndpointsRequest.EndpointUrl}'.");
            var getEndpointsResponse = await UaTcpDiscoveryClient.GetEndpointsAsync(getEndpointsRequest);

            // for each endpoint and user identity type, try creating a session and reading a few nodes.
            foreach (var selectedEndpoint in getEndpointsResponse.Endpoints.OrderBy(e => e.SecurityLevel))
            {
                foreach (var selectedTokenPolicy in selectedEndpoint.UserIdentityTokens)
                {
                    IUserIdentity selectedUserIdentity;
                    switch (selectedTokenPolicy.TokenType)
                    {
                        case UserTokenType.UserName:
                            selectedUserIdentity = new UserNameIdentity("root", "secret");
                            break;

                        case UserTokenType.Certificate:
                            selectedUserIdentity = new X509Identity(localCertificate);
                            break;

                        default:
                            selectedUserIdentity = new AnonymousIdentity();
                            break;
                    }

                    var client = new UaTcpSessionClient(
                        this.localDescription,
                        localCertificate,
                        selectedUserIdentity,
                        selectedEndpoint);
                    Console.WriteLine($"Creating session with endpoint '{client.RemoteEndpoint.EndpointUrl}'.");
                    Console.WriteLine($"SecurityPolicy: '{client.RemoteEndpoint.SecurityPolicyUri}'.");
                    Console.WriteLine($"SecurityMode: '{client.RemoteEndpoint.SecurityMode}'.");
                    Console.WriteLine($"UserIdentityToken: '{client.UserIdentity}'.");
                    try
                    {
                        await client.OpenAsync();
                        Console.WriteLine($"Closing session '{client.SessionId}'.");
                        await client.CloseAsync();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Error opening session '{client.SessionId}'. {ex.Message}");
                    }
                }
            }
        }
        public async Task TestSubscription()
        {
            // get or add application certificate.
            var localCertificate = this.localDescription.GetCertificate();
            if (localCertificate == null)
            {
                throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Application certificate is missing.");
            }

            // discover available endpoints of server.
            var getEndpointsRequest = new GetEndpointsRequest
            {
                EndpointUrl = this.endpointUrl,
                ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
            };
            Console.WriteLine($"Discovering endpoints of '{getEndpointsRequest.EndpointUrl}'.");
            var getEndpointsResponse = await UaTcpDiscoveryClient.GetEndpointsAsync(getEndpointsRequest);
            var selectedEndpoint = getEndpointsResponse.Endpoints.OrderBy(e => e.SecurityLevel).Last();

            IUserIdentity selectedUserIdentity = new UserNameIdentity("root", "secret");

            var session = new UaTcpSessionService(
                this.localDescription,
                localCertificate,
                selectedUserIdentity,
                selectedEndpoint);
            Console.WriteLine($"Creating session with endpoint '{session.RemoteEndpoint.EndpointUrl}'.");
            Console.WriteLine($"SecurityPolicy: '{session.RemoteEndpoint.SecurityPolicyUri}'.");
            Console.WriteLine($"SecurityMode: '{session.RemoteEndpoint.SecurityMode}'.");

            var sub = new MySubscription(session)
            {
                PublishingInterval = 1000,
                KeepAliveCount = 20,
                PublishingEnabled = true
            };



            Console.WriteLine($"Created subscription.");

            await Task.Delay(5000);

            Assert.IsTrue(sub.ServerServerStatusCurrentTime != DateTime.MinValue);

            session.Dispose();

        }
        public async Task TransferSubscriptions()
        {
            // get or add application certificate.
            var localCertificate = this.localDescription.GetCertificate();
            if (localCertificate == null)
            {
                throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Application certificate is missing.");
            }

            // discover available endpoints of server.
            var getEndpointsRequest = new GetEndpointsRequest
            {
                EndpointUrl = this.endpointUrl,
                ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
            };
            Console.WriteLine($"Discovering endpoints of '{getEndpointsRequest.EndpointUrl}'.");
            var getEndpointsResponse = await UaTcpDiscoveryClient.GetEndpointsAsync(getEndpointsRequest);
            var selectedEndpoint = getEndpointsResponse.Endpoints.OrderBy(e => e.SecurityLevel).Last();

            IUserIdentity selectedUserIdentity = new UserNameIdentity("root", "secret");

            var client = new UaTcpSessionClient(
                this.localDescription,
                localCertificate,
                selectedUserIdentity,
                selectedEndpoint);
            Console.WriteLine($"Creating session with endpoint '{client.RemoteEndpoint.EndpointUrl}'.");
            Console.WriteLine($"SecurityPolicy: '{client.RemoteEndpoint.SecurityPolicyUri}'.");
            Console.WriteLine($"SecurityMode: '{client.RemoteEndpoint.SecurityMode}'.");
            await client.OpenAsync();
            Console.WriteLine($"Activated session '{client.SessionId}'.");
            var req = new CreateSubscriptionRequest
            {
                RequestedPublishingInterval = 1000,
                RequestedMaxKeepAliveCount = 20,
                PublishingEnabled = true
            };
            var res = await client.CreateSubscriptionAsync(req);
            Console.WriteLine($"Created subscription '{res.SubscriptionId}'.");

            Console.WriteLine($"Aborting session '{client.SessionId}'.");
            await client.AbortAsync();

            var client2 = new UaTcpSessionClient(
                this.localDescription,
                localCertificate,
                selectedUserIdentity,
                selectedEndpoint);
            await client2.OpenAsync();
            Console.WriteLine($"Activated session '{client2.SessionId}'.");

            var req2 = new TransferSubscriptionsRequest
            {
                SubscriptionIds = new[] { res.SubscriptionId }
            };
            var res2 = await client2.TransferSubscriptionsAsync(req2);
            Console.WriteLine($"Transferred subscription result '{res2.Results[0].StatusCode}'.");

            Assert.IsTrue(StatusCode.IsGood(res2.Results[0].StatusCode));

            Console.WriteLine($"Closing session '{client2.SessionId}'.");
            await client2.CloseAsync();
        }
        public async Task SessionTimeoutCausesFault()
        {
            // get or add application certificate.
            var localCertificate = this.localDescription.GetCertificate();
            if (localCertificate == null)
            {
                throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Application certificate is missing.");
            }

            // discover available endpoints of server.
            var getEndpointsRequest = new GetEndpointsRequest
            {
                EndpointUrl = this.endpointUrl,
                ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
            };
            Console.WriteLine($"Discovering endpoints of '{getEndpointsRequest.EndpointUrl}'.");
            var getEndpointsResponse = await UaTcpDiscoveryClient.GetEndpointsAsync(getEndpointsRequest);
            var selectedEndpoint = getEndpointsResponse.Endpoints.OrderBy(e => e.SecurityLevel).Last();

            var selectedTokenType = selectedEndpoint.UserIdentityTokens[0].TokenType;
            IUserIdentity selectedUserIdentity;
            switch (selectedTokenType)
            {
                case UserTokenType.UserName:
                    selectedUserIdentity = new UserNameIdentity("root", "secret");
                    break;

                case UserTokenType.Certificate:
                    selectedUserIdentity = new X509Identity(localCertificate);
                    break;

                default:
                    selectedUserIdentity = new AnonymousIdentity();
                    break;
            }

            var client = new UaTcpSessionClient(
                this.localDescription,
                localCertificate,
                selectedUserIdentity,
                selectedEndpoint)
            {
                SessionTimeout = 10000
            };
            Console.WriteLine($"Creating session with endpoint '{client.RemoteEndpoint.EndpointUrl}'.");
            Console.WriteLine($"SecurityPolicy: '{client.RemoteEndpoint.SecurityPolicyUri}'.");
            Console.WriteLine($"SecurityMode: '{client.RemoteEndpoint.SecurityMode}'.");
            await client.OpenAsync();
            Console.WriteLine($"Activated session '{client.SessionId}'.");

            // server should close session due to inactivity
            await Task.Delay(20000);

            // should throw exception
            var readRequest = new ReadRequest { NodesToRead = new[] { new ReadValueId { NodeId = NodeId.Parse(VariableIds.Server_ServerStatus_CurrentTime), AttributeId = AttributeIds.Value } } };
            await client.ReadAsync(readRequest);

            Console.WriteLine($"Closing session '{client.SessionId}'.");
            await client.CloseAsync();
        }
        /// <summary>
        /// Opens the session with the remote endpoint.
        /// </summary>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <returns>A task.</returns>
        private async Task OpenSessionAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            await this.semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
            try
            {
                if (this.RemoteEndpoint == null)
                {
                    // If specific endpoint is not provided, use discovery to select endpoint with highest
                    // security level.
                    try
                    {
                        Log.Info($"Discovering endpoints of '{this.discoveryUrl}'.");
                        var getEndpointsRequest = new GetEndpointsRequest
                        {
                            EndpointUrl = this.discoveryUrl,
                            ProfileUris = new[] { TransportProfileUris.UaTcpTransport }
                        };
                        var getEndpointsResponse = await UaTcpDiscoveryClient.GetEndpointsAsync(getEndpointsRequest);
                        if (getEndpointsResponse.Endpoints == null || getEndpointsResponse.Endpoints.Length == 0)
                        {
                            throw new InvalidOperationException($"'{this.discoveryUrl}' returned no endpoints.");
                        }

                        this.RemoteEndpoint = getEndpointsResponse.Endpoints.OrderBy(e => e.SecurityLevel).Last();
                    }
                    catch (Exception ex)
                    {
                        Log.Warn($"Error discovering endpoints of '{this.discoveryUrl}'. {ex.Message}");
                        throw;
                    }
                }

                // throw here to exit state machine.
                cancellationToken.ThrowIfCancellationRequested();
                try
                {
                    Log.Info($"Opening UaTcpSessionChannel with endpoint '{this.RemoteEndpoint.EndpointUrl}'.");
                    this.innerChannel = new UaTcpSessionChannel(this.LocalDescription, this.LocalCertificate, this.UserIdentity, this.RemoteEndpoint)
                    {
                        SessionTimeout = this.SessionTimeout,
                        TimeoutHint = this.TimeoutHint,
                        DiagnosticsHint = this.DiagnosticsHint,
                        LocalSendBufferSize = this.LocalSendBufferSize,
                        LocalReceiveBufferSize = this.LocalReceiveBufferSize,
                        LocalMaxMessageSize = this.LocalMaxMessageSize,
                        LocalMaxChunkCount = this.LocalMaxChunkCount,
                    };

                    await this.innerChannel.OpenAsync(cancellationToken);
                }
                catch (Exception ex)
                {
                    Log.Warn($"Error opening UaTcpSessionChannel with endpoint '{this.RemoteEndpoint.EndpointUrl}'. {ex.Message}");
                    throw;
                }
            }
            finally
            {
                this.semaphore.Release();
            }
        }