/// <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();
            }
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="UaTcpSessionClient"/> class.
 /// </summary>
 /// <param name="localDescription">The <see cref="ApplicationDescription"/> of the local application.</param>
 /// <param name="localCertificate">The <see cref="X509Certificate2"/> of the local application.</param>
 /// <param name="userIdentity">The user identity or null if anonymous. Supports <see cref="AnonymousIdentity"/>, <see cref="UserNameIdentity"/>, <see cref="IssuedIdentity"/> and <see cref="X509Identity"/>.</param>
 /// <param name="remoteEndpoint">The <see cref="EndpointDescription"/> of the remote application. Obtained from a prior call to UaTcpDiscoveryClient.GetEndpoints.</param>
 public UaTcpSessionClient(ApplicationDescription localDescription, X509Certificate2 localCertificate, IUserIdentity userIdentity, EndpointDescription remoteEndpoint)
 {
     this.innerChannel = new UaTcpSessionChannel(localDescription, localCertificate, userIdentity, remoteEndpoint);
     this.semaphore = new SemaphoreSlim(1);
 }