/*
         *   Negotiation consists of the following steps (some may be async in the async case):
         *   1. Create negotiation state
         *   2. Initialize channel factories
         *   3. Create an channel
         *   4. Open the channel
         *   5. Create the next message to send to server
         *   6. Send the message and get reply
         *   8. Process incoming message and get next outgoing message.
         *   9. If no outgoing message, then negotiation is over. Go to step 11.
         *   10. Goto step 6
         *   11. Close the IAsyncRequest channel and complete
         */
        protected async Task <SecurityToken> DoNegotiationAsync(TimeSpan timeout)
        {
            ThrowIfClosedOrCreated();
            TimeoutHelper        timeoutHelper = new TimeoutHelper(timeout);
            IAsyncRequestChannel rstChannel    = null;
            T        negotiationState          = null;
            TimeSpan timeLeft = timeout;
            int      legs     = 1;

            try
            {
                negotiationState = await CreateNegotiationStateAsync(_targetAddress, _via, timeoutHelper.RemainingTime());

                InitializeNegotiationState(negotiationState);
                await InitializeChannelFactoriesAsync(negotiationState.RemoteAddress, timeoutHelper.RemainingTime());

                rstChannel = CreateClientChannel(negotiationState.RemoteAddress, _via);
                await rstChannel.OpenAsync(timeoutHelper.RemainingTime());

                Message       nextOutgoingMessage = null;
                Message       incomingMessage     = null;
                SecurityToken serviceToken        = null;
                for (; ;)
                {
                    nextOutgoingMessage = GetNextOutgoingMessage(incomingMessage, negotiationState);
                    if (incomingMessage != null)
                    {
                        incomingMessage.Close();
                    }

                    if (nextOutgoingMessage != null)
                    {
                        using (nextOutgoingMessage)
                        {
                            timeLeft        = timeoutHelper.RemainingTime();
                            incomingMessage = await rstChannel.RequestAsync(nextOutgoingMessage, timeLeft);

                            if (incomingMessage == null)
                            {
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(SR.FailToRecieveReplyFromNegotiation));
                            }
                        }
                        legs += 2;
                    }
                    else
                    {
                        if (!negotiationState.IsNegotiationCompleted)
                        {
                            throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.NoNegotiationMessageToSend), incomingMessage);
                        }

                        try
                        {
                            rstChannel.Close(timeoutHelper.RemainingTime());
                        }
                        catch (CommunicationException)
                        {
                            rstChannel.Abort();
                        }
                        catch (TimeoutException)
                        {
                            rstChannel.Abort();
                        }

                        rstChannel = null;
                        ValidateAndCacheServiceToken(negotiationState);
                        serviceToken = negotiationState.ServiceToken;
                        break;
                    }
                }
                return(serviceToken);
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }

                if (e is TimeoutException)
                {
                    e = new TimeoutException(SR.Format(SR.ClientSecurityNegotiationTimeout, timeout, legs, timeLeft), e);
                }

                EndpointAddress temp = (negotiationState == null) ? null : negotiationState.RemoteAddress;
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(WrapExceptionIfRequired(e, temp, _issuerAddress));
            }
            finally
            {
                Cleanup(rstChannel, negotiationState);
            }
        }
        private async Task <SecurityToken> DoOperationAsync(SecuritySessionOperation operation, EndpointAddress target, Uri via, SecurityToken currentToken, TimeSpan timeout)
        {
            if (target == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(target));
            }

            if (operation == SecuritySessionOperation.Renew && currentToken == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(currentToken));
            }

            IAsyncRequestChannel channel = null;

            try
            {
                channel = CreateChannel(operation, target, via);

                TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
                await channel.OpenAsync(timeoutHelper.RemainingTime());

                object requestState;
                GenericXmlSecurityToken issuedToken;

                using (Message requestMessage = CreateRequest(operation, target, currentToken, out requestState))
                {
                    EventTraceActivity eventTraceActivity = null;

                    TraceUtility.ProcessOutgoingMessage(requestMessage, eventTraceActivity);

                    using (Message reply = await channel.RequestAsync(requestMessage, timeoutHelper.RemainingTime()))
                    {
                        if (reply == null)
                        {
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(SR.FailToRecieveReplyFromNegotiation));
                        }

                        TraceUtility.ProcessIncomingMessage(reply, eventTraceActivity);
                        ThrowIfFault(reply, _targetAddress);
                        issuedToken = ProcessReply(reply, operation, requestState);
                        ValidateKeySize(issuedToken);
                    }
                }
                await channel.CloseAsync(timeoutHelper.RemainingTime());

                OnOperationSuccess(operation, target, issuedToken, currentToken);
                return(issuedToken);
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }

                if (e is TimeoutException)
                {
                    e = new TimeoutException(SR.Format(SR.ClientSecuritySessionRequestTimeout, timeout), e);
                }

                OnOperationFailure(operation, target, currentToken, e, channel);
                throw;
            }
        }