public SIPTransaction GetTransaction(SIPResponse sipResponse) { if (sipResponse.Header.Vias.TopViaHeader.Branch == null || sipResponse.Header.Vias.TopViaHeader.Branch.Trim().Length == 0) { return(null); } else { string transactionId = SIPTransaction.GetRequestTransactionId(sipResponse.Header.Vias.TopViaHeader.Branch, sipResponse.Header.CSeqMethod); lock (m_transactions) { if (m_transactions.ContainsKey(transactionId)) { return(m_transactions[transactionId]); } else { return(null); } } } }
/// <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); } } }