/// <summary>
 /// Default constructor.
 /// </summary>
 /// <param name="stack">Reference to SIP stack.</param>
 /// <param name="transaction">Client transaction what response it is. This value can be null if no matching client response.</param>
 /// <param name="response">Received response.</param>
 internal SIP_ResponseReceivedEventArgs(SIP_Stack stack,
                                        SIP_ClientTransaction transaction,
                                        SIP_Response response)
 {
     m_pStack = stack;
     m_pResponse = response;
     m_pTransaction = transaction;
 }
        /// <summary>
        /// Creates new client transaction.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to send request.</param>
        /// <param name="request">SIP request that transaction will handle.</param>
        /// <param name="addVia">If true, transaction will add <b>Via:</b> header, otherwise it's user responsibility.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        /// <returns>Returns created transaction.</returns>
        public SIP_ClientTransaction CreateClientTransaction(SIP_Flow flow, SIP_Request request, bool addVia)
        {
            if (m_IsDisposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            // Add Via:
            if (addVia)
            {
                SIP_t_ViaParm via = new SIP_t_ViaParm();
                via.ProtocolName = "SIP";
                via.ProtocolVersion = "2.0";
                via.ProtocolTransport = flow.Transport;
                via.SentBy = new HostEndPoint("transport_layer_will_replace_it", -1);
                via.Branch = SIP_t_ViaParm.CreateBranch();
                via.RPort = 0;
                request.Via.AddToTop(via.ToStringValue());
            }

            lock (m_pClientTransactions)
            {
                SIP_ClientTransaction transaction = new SIP_ClientTransaction(m_pStack, flow, request);
                m_pClientTransactions.Add(transaction.Key, transaction);
                transaction.StateChanged += delegate
                                                {
                                                    if (transaction.State == SIP_TransactionState.Terminated)
                                                    {
                                                        lock (m_pClientTransactions)
                                                        {
                                                            m_pClientTransactions.Remove(transaction.Key);
                                                        }
                                                    }
                                                };

                return transaction;
            }
        }
        /// <summary>
        /// Sends request to the specified flow.
        /// </summary>
        /// <param name="flow">Data flow.</param>
        /// <param name="request">SIP request.</param>
        /// <param name="transaction">Owner client transaction or null if stateless sending.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments contains invalid value.</exception>
        internal void SendRequest(SIP_Flow flow, SIP_Request request, SIP_ClientTransaction transaction)
        {
            if (m_IsDisposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            if (request.Via.GetTopMostValue() == null)
            {
                throw new ArgumentException("Argument 'request' doesn't contain required Via: header field.");
            }

            // Set sent-by
            SIP_t_ViaParm via = request.Via.GetTopMostValue();
            via.ProtocolTransport = flow.Transport;
            // Via sent-by is used only to send responses when request maker data flow is not active.
            // Normally this never used, so just report first local listening point as sent-by.
            HostEndPoint sentBy = null;
            foreach (IPBindInfo bind in BindInfo)
            {
                if (flow.Transport == SIP_Transport.UDP && bind.Protocol == BindInfoProtocol.UDP)
                {
                    if (!string.IsNullOrEmpty(bind.HostName))
                    {
                        sentBy = new HostEndPoint(bind.HostName, bind.Port);
                    }
                    else
                    {
                        sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(), bind.Port);
                    }
                    break;
                }
                else if (flow.Transport == SIP_Transport.TLS && bind.Protocol == BindInfoProtocol.TCP &&
                         bind.SslMode == SslMode.SSL)
                {
                    if (!string.IsNullOrEmpty(bind.HostName))
                    {
                        sentBy = new HostEndPoint(bind.HostName, bind.Port);
                    }
                    else
                    {
                        sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(), bind.Port);
                    }
                    break;
                }
                else if (flow.Transport == SIP_Transport.TCP && bind.Protocol == BindInfoProtocol.TCP)
                {
                    if (!string.IsNullOrEmpty(bind.HostName))
                    {
                        sentBy = new HostEndPoint(bind.HostName, bind.Port);
                    }
                    else
                    {
                        sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(), bind.Port);
                    }
                    break;
                }
            }
            // No local end point for sent-by, just use flow local end point for it.
            if (sentBy == null)
            {
                via.SentBy = new HostEndPoint(flow.LocalEP);
            }
            else
            {
                via.SentBy = sentBy;
            }

            // Send request.
            flow.Send(request);

            // Log.
            if (m_pStack.Logger != null)
            {
                byte[] requestData = request.ToByteData();

                m_pStack.Logger.AddWrite(Guid.NewGuid().ToString(),
                                         null,
                                         0,
                                         "Request [" +
                                         (transaction == null ? "" : "transactionID='" + transaction.ID + "';") +
                                         "method='" + request.RequestLine.Method + "'; cseq='" +
                                         request.CSeq.SequenceNumber + "'; " + "transport='" + flow.Transport +
                                         "'; size='" + requestData.Length + "'; sent '" + flow.LocalEP +
                                         "' -> '" + flow.RemoteEP + "'.",
                                         flow.LocalEP,
                                         flow.RemoteEP,
                                         requestData);
            }
        }
        /// <summary>
        /// Cleans up active transaction.
        /// </summary>
        private void CleanUpActiveTransaction()
        {
            if (m_pTransaction != null)
            {
                // Don't dispose transaction, transaction will dispose itself when done.
                // Otherwise for example failed INVITE won't linger in "Completed" state as it must be.
                // We just release Events processing, because you don't care about them any more.
                m_pTransaction.ResponseReceived -= ClientTransaction_ResponseReceived;
                m_pTransaction.TimedOut -= ClientTransaction_TimedOut;
                m_pTransaction.TransportError -= ClientTransaction_TransportError;

                m_pTransaction = null;
            }
        }
        /// <summary>
        /// Sends specified request to the specified data flow.
        /// </summary>
        /// <param name="flow">SIP data flow.</param>
        /// <param name="request">SIP request to send.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        private void SendToFlow(SIP_Flow flow, SIP_Request request)
        {
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            #region Contact (RFC 3261 8.1.1.8)

            /*
                The Contact header field provides a SIP or SIPS URI that can be used
                to contact that specific instance of the UA for subsequent requests.
                The Contact header field MUST be present and contain exactly one SIP
                or SIPS URI in any request that can result in the establishment of a
                dialog.  For the methods defined in this specification, that includes
                only the INVITE request.  For these requests, the scope of the
                Contact is global.  That is, the Contact header field value contains
                the URI at which the UA would like to receive requests, and this URI
                MUST be valid even if used in subsequent requests outside of any
                dialogs.

                If the Request-URI or top Route header field value contains a SIPS
                URI, the Contact header field MUST contain a SIPS URI as well.
            */

            SIP_t_ContactParam contact = request.Contact.GetTopMostValue();

            // Add contact header If request-Method can establish dialog and contact header not present.            
            if (SIP_Utils.MethodCanEstablishDialog(request.RequestLine.Method) && contact == null)
            {
                SIP_Uri from = (SIP_Uri) request.From.Address.Uri;

                request.Contact.Add((flow.IsSecure ? "sips:" : "sip:") + from.User + "@" +
                                    m_pStack.TransportLayer.GetContactHost(flow));
            }
                // If contact SIP URI and host = auto-allocate, allocate it as needed.
            else if (contact != null && contact.Address.Uri is SIP_Uri &&
                     ((SIP_Uri) contact.Address.Uri).Host == "auto-allocate")
            {
                ((SIP_Uri) contact.Address.Uri).Host =
                    m_pStack.TransportLayer.GetContactHost(flow).ToString();
            }

            #endregion

            m_pTransaction = m_pStack.TransactionLayer.CreateClientTransaction(flow, request, true);
            m_pTransaction.ResponseReceived += ClientTransaction_ResponseReceived;
            m_pTransaction.TimedOut += ClientTransaction_TimedOut;
            m_pTransaction.TransportError += ClientTransaction_TransportError;

            // Start transaction processing.
            m_pTransaction.Start();
        }
        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public void Dispose()
        {
            lock (m_pLock)
            {
                if (m_State == SIP_RequestSenderState.Disposed)
                {
                    return;
                }
                m_State = SIP_RequestSenderState.Disposed;

                OnDisposed();

                ResponseReceived = null;
                Completed = null;
                Disposed = null;

                m_pStack = null;
                m_pRequest = null;
                m_pCredentials = null;
                m_pHops = null;
                m_pTransaction = null;
                m_pLock = null;
            }
        }