Example #1
0
        /// <summary>
        /// Transaction matching see RFC3261 17.1.3 & 17.2.3 for matching client and server transactions respectively.
        /// IMPORTANT NOTE this transaction matching applies to all requests and responses EXCEPT ACK requests to 2xx responses see 13.2.2.4.
        /// For ACK's to 2xx responses the ACK represents a separate transaction. However for a UAS sending an INVITE response the ACK still has to be
        /// matched to an existing server transaction in order to transition it to a Confirmed state.
        ///
        /// ACK's:
        ///  - The ACK for a 2xx response will have the same CallId, From Tag and To Tag.
        ///  - An ACK for a non-2xx response will have the same branch ID as the INVITE whose response it acknowledges.
        /// </summary>
        /// <param name="sipRequest"></param>
        /// <returns></returns>
        public SIPTransaction GetTransaction(SIPRequest sipRequest)
        {
            // The branch is mandatory but it doesn't stop some UA's not setting it.
            if (sipRequest.Header.Vias.TopViaHeader.Branch == null || sipRequest.Header.Vias.TopViaHeader.Branch.Trim().Length == 0)
            {
                return(null);
            }

            SIPMethodsEnum transactionMethod = (sipRequest.Method != SIPMethodsEnum.ACK) ? sipRequest.Method : SIPMethodsEnum.INVITE;
            string         transactionId     = SIPTransaction.GetRequestTransactionId(sipRequest.Header.Vias.TopViaHeader.Branch, transactionMethod);
            string         contactAddress    = (sipRequest.Header.Contact != null && sipRequest.Header.Contact.Count > 0) ? sipRequest.Header.Contact[0].ToString() : "no contact";

            lock (m_transactions)
            {
                //if (transactionMethod == SIPMethodsEnum.ACK)
                //{
                //logger.Info("Matching ACK with contact=" + contactAddress + ", cseq=" + sipRequest.Header.CSeq + ".");
                //}

                if (transactionId != null && m_transactions.ContainsKey(transactionId))
                {
                    //if (transactionMethod == SIPMethodsEnum.ACK)
                    //{
                    //logger.Info("ACK for contact=" + contactAddress + ", cseq=" + sipRequest.Header.CSeq + " was matched by branchid.");
                    //}

                    return(m_transactions[transactionId]);
                }
                else
                {
                    // No normal match found so look fo a 2xx INVITE response waiting for an ACK.
                    if (sipRequest.Method == SIPMethodsEnum.ACK)
                    {
                        //logger.Debug("Looking for ACK transaction, branchid=" + sipRequest.Header.Via.TopViaHeader.Branch + ".");

                        foreach (SIPTransaction transaction in m_transactions.Values)
                        {
                            // According to the standard an ACK should only not get matched by the branchid on the original INVITE for a non-2xx response. However
                            // my Cisco phone created a new branchid on ACKs to 487 responses and since the Cisco also used the same Call-ID and From tag on the initial
                            // unauthenticated request and the subsequent authenticated request the condition below was found to be the best way to match the ACK.

                            /*if (transaction.TransactionType == SIPTransactionTypesEnum.Invite && transaction.TransactionFinalResponse != null && transaction.TransactionState == SIPTransactionStatesEnum.Completed)
                             * {
                             *  if (transaction.TransactionFinalResponse.Header.CallId == sipRequest.Header.CallId &&
                             *      transaction.TransactionFinalResponse.Header.To.ToTag == sipRequest.Header.To.ToTag &&
                             *      transaction.TransactionFinalResponse.Header.From.FromTag == sipRequest.Header.From.FromTag)
                             *  {
                             *      return transaction;
                             *  }
                             * }*/

                            // As an experiment going to try matching on the Call-ID. This field seems to be unique and therefore the chance
                            // of collisions seemingly very slim. As a safeguard if there happen to be two transactions with the same Call-ID in the list the match will not be made.
                            // One case where the Call-Id match breaks down is for in-Dialogue requests in that case there will be multiple transactions with the same Call-ID and tags.
                            //if (transaction.TransactionType == SIPTransactionTypesEnum.Invite && transaction.TransactionFinalResponse != null && transaction.TransactionState == SIPTransactionStatesEnum.Completed)
                            if (transaction.TransactionType == SIPTransactionTypesEnum.Invite && transaction.TransactionFinalResponse != null)
                            {
                                if (transaction.TransactionRequest.Header.CallId == sipRequest.Header.CallId &&
                                    transaction.TransactionFinalResponse.Header.To.ToTag == sipRequest.Header.To.ToTag &&
                                    transaction.TransactionFinalResponse.Header.From.FromTag == sipRequest.Header.From.FromTag &&
                                    transaction.TransactionFinalResponse.Header.CSeq == sipRequest.Header.CSeq)
                                {
                                    //logger.Info("ACK for contact=" + contactAddress + ", cseq=" + sipRequest.Header.CSeq + " was matched by callid, tags and cseq.");

                                    return(transaction);
                                }
                                else if (transaction.TransactionRequest.Header.CallId == sipRequest.Header.CallId &&
                                         transaction.TransactionFinalResponse.Header.CSeq == sipRequest.Header.CSeq &&
                                         IsCallIdUniqueForPending(sipRequest.Header.CallId))
                                {
                                    string requestEndPoint = (sipRequest.RemoteSIPEndPoint != null) ? sipRequest.RemoteSIPEndPoint.ToString() : " ? ";
                                    //logger.Info("ACK for contact=" + contactAddress + ", cseq=" + sipRequest.Header.CSeq + " was matched using Call-ID mechanism (to tags: " + transaction.TransactionFinalResponse.Header.To.ToTag + "=" + sipRequest.Header.To.ToTag + ", from tags:" + transaction.TransactionFinalResponse.Header.From.FromTag + "=" + sipRequest.Header.From.FromTag + ").");
                                    return(transaction);
                                }
                            }
                        }

                        //logger.Info("ACK for contact=" + contactAddress + ", cseq=" + sipRequest.Header.CSeq + " was not matched.");
                    }

                    return(null);
                }
            }
        }
Example #2
0
 void transaction_TransactionStateChanged(SIPTransaction sipTransaction)
 {
     Console.WriteLine(sipTransaction.GetType() + " State Change (" + sipTransaction.TransactionId + "): " + sipTransaction.TransactionState);
 }
Example #3
0
 void transaction_TransactionRemoved(SIPTransaction sipTransaction)
 {
     Console.WriteLine(sipTransaction.GetType() + " Removed (" + sipTransaction.TransactionId + ")");
 }
Example #4
0
 void SetTransactionTraceEvents(SIPTransaction transaction)
 {
     transaction.TransactionRemoved      += new SIPTransactionRemovedDelegate(transaction_TransactionRemoved);
     transaction.TransactionStateChanged += new SIPTransactionStateChangeDelegate(transaction_TransactionStateChanged);
     transaction.TransactionTraceMessage += new SIPTransactionTraceMessageDelegate(transaction_TransactionTraceMessage);
 }
Example #5
0
 void transaction_TransactionTraceMessage(SIPTransaction sipTransaction, string message)
 {
     //Console.WriteLine(sipTransaction.GetType() + " Trace (" + sipTransaction.TransactionId + "): " + message);
 }
        private void UASInviteTransaction_TransactionRequestReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPRequest sipRequest)
        {
            try
            {
                if (TransactionState == SIPTransactionStatesEnum.Terminated)
                {
                    logger.Debug("Request received by UASInviteTransaction for a terminated transaction, ignoring.");
                }
                else if (sipRequest.Method != SIPMethodsEnum.INVITE)
                {
                    logger.Warn("Unexpected " + sipRequest.Method + " passed to UASInviteTransaction.");
                }
                else
                {
                    if (TransactionState != SIPTransactionStatesEnum.Trying)
                    {
                        SIPResponse tryingResponse = GetInfoResponse(m_transactionRequest, SIPResponseStatusCodesEnum.Trying);
                        SendInformationalResponse(tryingResponse);
                    }

                    // Notify new call subscribers.
                    if (NewCallReceived != null)
                    {
                        NewCallReceived(localSIPEndPoint, remoteEndPoint, this, sipRequest);
                    }
                    else
                    {
                        // Nobody wants the call so return an error response.
                        SIPResponse declinedResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Decline, "Nothing listening");
                        SendFinalResponse(declinedResponse);
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception UASInviteTransaction GotRequest. " + excp.Message);
            }
        }
Example #7
0
        public void RemoveExpiredTransactions()
        {
            try
            {
                lock (m_transactions)
                {
                    List <string> expiredTransactionIds = new List <string>();

                    foreach (SIPTransaction transaction in m_transactions.Values)
                    {
                        if (transaction.TransactionType == SIPTransactionTypesEnum.Invite)
                        {
                            if (transaction.TransactionState == SIPTransactionStatesEnum.Confirmed)
                            {
                                // Need to wait until the transaction timeout period is reached in case any ACK re-transmits are received.
                                if (DateTime.Now.Subtract(transaction.CompletedAt).TotalMilliseconds >= m_t6)
                                {
                                    expiredTransactionIds.Add(transaction.TransactionId);
                                }
                            }
                            else if (transaction.TransactionState == SIPTransactionStatesEnum.Completed)
                            {
                                if (DateTime.Now.Subtract(transaction.CompletedAt).TotalMilliseconds >= m_t6)
                                {
                                    expiredTransactionIds.Add(transaction.TransactionId);
                                }
                            }
                            else if (transaction.HasTimedOut)
                            {
                                // For INVITES need to give timed out transactions time to send the reliable repsonses and receive the ACKs.
                                if (DateTime.Now.Subtract(transaction.TimedOutAt).TotalSeconds >= m_t6)
                                {
                                    expiredTransactionIds.Add(transaction.TransactionId);
                                }
                            }
                            else if (transaction.TransactionState == SIPTransactionStatesEnum.Proceeding)
                            {
                                if (DateTime.Now.Subtract(transaction.Created).TotalMilliseconds >= m_maxRingTime)
                                {
                                    // INVITE requests that have been ringing too long.
                                    transaction.HasTimedOut     = true;
                                    transaction.TimedOutAt      = DateTime.Now;
                                    transaction.DeliveryPending = false;
                                    transaction.DeliveryFailed  = true;
                                    transaction.FireTransactionTimedOut();
                                }
                            }
                            else if (DateTime.Now.Subtract(transaction.Created).TotalMilliseconds >= m_t6)
                            {
                                //logger.Debug("INVITE transaction (" + transaction.TransactionId + ") " + transaction.TransactionRequestURI.ToString() + " in " + transaction.TransactionState + " has been alive for " + DateTime.Now.Subtract(transaction.Created).TotalSeconds.ToString("0") + ".");

                                if (transaction.TransactionState == SIPTransactionStatesEnum.Calling ||
                                    transaction.TransactionState == SIPTransactionStatesEnum.Trying)
                                {
                                    transaction.HasTimedOut     = true;
                                    transaction.TimedOutAt      = DateTime.Now;
                                    transaction.DeliveryPending = false;
                                    transaction.DeliveryFailed  = true;
                                    transaction.FireTransactionTimedOut();
                                }
                            }
                        }
                        else if (transaction.HasTimedOut)
                        {
                            expiredTransactionIds.Add(transaction.TransactionId);
                        }
                        else if (DateTime.Now.Subtract(transaction.Created).TotalMilliseconds >= m_t6)
                        {
                            if (transaction.TransactionState == SIPTransactionStatesEnum.Calling ||
                                transaction.TransactionState == SIPTransactionStatesEnum.Trying ||
                                transaction.TransactionState == SIPTransactionStatesEnum.Proceeding)
                            {
                                //logger.Warn("Timed out transaction in SIPTransactionEngine, should have been timed out in the SIP Transport layer. " + transaction.TransactionRequest.Method + ".");
                                transaction.DeliveryPending = false;
                                transaction.DeliveryFailed  = true;
                                transaction.TimedOutAt      = DateTime.Now;
                                transaction.HasTimedOut     = true;
                                transaction.FireTransactionTimedOut();
                            }

                            expiredTransactionIds.Add(transaction.TransactionId);
                        }
                    }

                    foreach (string transactionId in expiredTransactionIds)
                    {
                        if (m_transactions.ContainsKey(transactionId))
                        {
                            SIPTransaction expiredTransaction = m_transactions[transactionId];
                            expiredTransaction.FireTransactionRemoved();
                            RemoveTransaction(expiredTransaction);
                        }
                    }
                }

                //PrintPendingTransactions();
            }
            catch (Exception excp)
            {
                logger.Error("Exception RemoveExpiredTransaction. " + excp.Message);
            }
        }
        private void UACInviteTransaction_TransactionFinalResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            try
            {
                // BranchId for 2xx responses needs to be a new one, non-2xx final responses use same one as original request.
                if (sipResponse.StatusCode >= 200 && sipResponse.StatusCode < 299)
                {
                    if (_sendOkAckManually == false)
                    {
                        Send2xxAckRequest(null, null);
                    }
                }
                else
                {
                    // ACK for non 2xx response is part of the INVITE transaction and gets routed to the same endpoint as the INVITE.
                    var ackRequest = GetInTransactionACKRequest(sipResponse, m_transactionRequest.URI, LocalSIPEndPoint);
                    base.SendRequest(RemoteEndPoint, ackRequest);
                }

                if (UACInviteTransactionFinalResponseReceived != null)
                {
                    UACInviteTransactionFinalResponseReceived(localSIPEndPoint, remoteEndPoint, sipTransaction, sipResponse);
                }

                if (CDR != null)
                {
                    SIPEndPoint localEP  = SIPEndPoint.TryParse(sipResponse.Header.ProxyReceivedOn) ?? localSIPEndPoint;
                    SIPEndPoint remoteEP = SIPEndPoint.TryParse(sipResponse.Header.ProxyReceivedFrom) ?? remoteEndPoint;
                    CDR.Answered(sipResponse.StatusCode, sipResponse.Status, sipResponse.ReasonPhrase, localEP, remoteEP);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception UACInviteTransaction_TransactionFinalResponseReceived. " + excp.Message);
            }
        }
 private void UASInviteTransaction_TransactionResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
 {
     logger.Warn("UASInviteTransaction received unexpected response, " + sipResponse.ReasonPhrase + " from " + remoteEndPoint.ToString() + ", ignoring.");
 }
        private void UACInviteTransaction_TransactionInformationResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            try
            {
                if (UACInviteTransactionInformationResponseReceived != null)
                {
                    UACInviteTransactionInformationResponseReceived(localSIPEndPoint, remoteEndPoint, sipTransaction, sipResponse);
                }

                if (CDR != null)
                {
                    SIPEndPoint localEP  = SIPEndPoint.TryParse(sipResponse.Header.ProxyReceivedOn) ?? localSIPEndPoint;
                    SIPEndPoint remoteEP = SIPEndPoint.TryParse(sipResponse.Header.ProxyReceivedFrom) ?? remoteEndPoint;
                    CDR.Progress(sipResponse.Status, sipResponse.ReasonPhrase, localEP, remoteEP);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception UACInviteTransaction_TransactionInformationResponseReceived. " + excp.Message);
            }
        }
 private void UACInviteTransaction_TransactionRequestReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPRequest sipRequest)
 {
     logger.Warn("UACInviteTransaction received unexpected request, " + sipRequest.Method + " from " + remoteEndPoint.ToString() + ", ignoring.");
 }
        private void SIPCancelTransaction_TransactionRequestReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPRequest sipRequest)
        {
            try
            {
                //logger.Debug("CANCEL request received, attempting to locate and cancel transaction.");

                //UASInviteTransaction originalTransaction = (UASInviteTransaction)GetTransaction(GetRequestTransactionId(sipRequest.Header.Via.TopViaHeader.Branch, SIPMethodsEnum.INVITE));

                SIPResponse cancelResponse;

                if (m_originalTransaction != null)
                {
                    //logger.Debug("Transaction found to cancel " + originalTransaction.TransactionId + " type " + originalTransaction.TransactionType + ".");
                    m_originalTransaction.CancelCall();
                    cancelResponse = GetCancelResponse(sipRequest, SIPResponseStatusCodesEnum.Ok);
                }
                else
                {
                    cancelResponse = GetCancelResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist);
                }

                //UpdateTransactionState(SIPTransactionStatesEnum.Completed);
                SendFinalResponse(cancelResponse);
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPCancelTransaction GotRequest. " + excp.Message);
            }
        }
 private void SIPCancelTransaction_TransactionFinalResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
 {
     if (sipResponse.StatusCode < 200)
     {
         logger.Warn("A SIP CANCEL transaction received an unexpected SIP information response " + sipResponse.ReasonPhrase + ".");
     }
     else
     {
         if (CancelTransactionFinalResponseReceived != null)
         {
             CancelTransactionFinalResponseReceived(localSIPEndPoint, remoteEndPoint, sipTransaction, sipResponse);
         }
     }
 }
 private void SIPCancelTransaction_TransactionRemoved(SIPTransaction transaction)
 {
     // Remove event handlers.
     CancelTransactionFinalResponseReceived = null;
 }