/// <summary> /// Starts processing server transaction. /// </summary> private void Begin() { // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) created."); // INVITE server transaction if(m_pRequest.Method == "INVITE"){ /* When a server transaction is constructed for a request, it enters the "Proceeding" state. The server transaction MUST generate a 100 (Trying) response. */ m_TransactionState = SIP_ServerTransactionState.Proceeding; SendTrying(); } // Non-INVITE server transaction else{ /* When a server transaction is constructed for a request, it enters the "Trying" state. */ m_TransactionState = SIP_ServerTransactionState.Trying; } // This is just timout timer and ensures that transaction ends or will terminated. m_pTransactionTimeoutTimer = new Timer(120 * m_T1); m_pTransactionTimeoutTimer.Elapsed += new ElapsedEventHandler(m_pTransactionTimeoutTimer_Elapsed); m_pTransactionTimeoutTimer.Enabled = true; // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) Transaction timeout Timer started, will triger after " + m_pTransactionTimeoutTimer.Interval + "."); }
/// <summary> /// Processes transaction request. /// </summary> /// <param name="request">SIP request.</param> internal void ProcessRequest(SIP_Request request) { // TODO: We MAY accept only ACK,CANCEL and request retransmission. // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) got request '" + request.Method + "'."); lock(this){ #region ACK if(request.Method == SIP_Methods.ACK){ /* RFC 3261 17.2.1. If an ACK is received while the server transaction is in the "Completed" state, the server transaction MUST transition to the "Confirmed" state. As Timer G is ignored in this state, any retransmissions of the response will cease. Also kill timer H(ACK wait timeout timer) because we got ACK. */ if(m_TransactionState == SIP_ServerTransactionState.Completed){ m_TransactionState = SIP_ServerTransactionState.Confirmed; // If there is timer G (response retransmit timer), stop it,because we got ACK. if(m_pTimerG != null){ m_pTimerG.Dispose(); m_pTimerG = null; // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) Timer G (response retransmit timer) stopped."); } // If there is timer H (ACK wait timeout timer), stop it because we got ACK. if(m_pTimerH != null){ m_pTimerH.Dispose(); m_pTimerH = null; // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) Timer H (ACK wait timeout timer) stopped."); } /* RFC 3261 17.2.1. The purpose of the "Confirmed" state is to absorb any additional ACK messages that arrive, triggered from retransmissions of the final response. When confirmed state is entered, timer I is set to fire in T4 seconds for unreliable transports, and zero seconds for reliable transports. */ if(request.Via.GetTopMostValue().ProtocolTransport.ToUpper() == "UDP"){ m_pTimerI = new Timer(m_T4); m_pTimerI.AutoReset = false; m_pTimerI.Elapsed += new ElapsedEventHandler(m_pTimerI_Elapsed); // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) Timer I (additional ACK absorb) started, will triger after " + m_pTimerI.Interval + "."); } else{ Dispose(); } } } #endregion #region CANCEL else if(request.Method == SIP_Methods.CANCEL){ // Do new server transaction for cancel. // We bind RFC here, rfc says UAS needs to respond canel, we hide cancel stuff from user. // If somebody knows why we can't do so, let me know. m_pSipStack.TransactionLayer.CreateServerTransaction(request).SendResponse(request.CreateResponse(SIP_ResponseCodes.x200_Ok)); Cancel(); } #endregion #region INVITE // INVITE server transaction else if(request.Method == SIP_Methods.INVITE){ /* If a retransmission of the request is received while in the "Proceeding" state, the most recently sent provisional response MUST be passed to the transport layer for retransmission. In the "Completed" state, the server transaction MUST pass the final response to the transport layer for retransmission whenever a retransmission of the request is received. */ if(m_TransactionState == SIP_ServerTransactionState.Proceeding){ SIP_Response lastProvisionalResponse = GetLastProvisionalResponse(); if(lastProvisionalResponse != null){ m_pSipStack.TransportLayer.SendResponse(request.Socket,lastProvisionalResponse); } } else if(m_TransactionState == SIP_ServerTransactionState.Completed){ m_pSipStack.TransportLayer.SendResponse(request.Socket,GetFinalResponse()); } } #endregion #region Non-INVITE // Non-INVITE server transaction else{ /* In the "Trying" state, any further request retransmissions are discarded. If a retransmission of the request is received while in the "Proceeding" state, the most recently sent provisional response MUST be passed to the transport layer for retransmission. In the "Completed" state, the server transaction MUST pass the final response to the transport layer for retransmission whenever a retransmission of the request is received. */ if(m_TransactionState == SIP_ServerTransactionState.Trying){ // Do nothing } else if(m_TransactionState == SIP_ServerTransactionState.Proceeding){ m_pSipStack.TransportLayer.SendResponse(request.Socket,GetLastProvisionalResponse()); } else if(m_TransactionState == SIP_ServerTransactionState.Completed){ m_pSipStack.TransportLayer.SendResponse(request.Socket,GetFinalResponse()); } } #endregion } }
/// <summary> /// Processes specified SIP response through this transaction. /// </summary> /// <param name="response">SIP response to process.</param> internal void ProcessResponse(SIP_Response response) { #region INVITE // INVITE server transaction if(m_pRequest.Method == "INVITE"){ #region Proceeding // Proceeding state if(m_TransactionState == SIP_ServerTransactionState.Proceeding){ // 1xx response if(response.StausCodeType == SIP_StatusCodeType.Provisional){ // We don't forward 100 Trying, skip it if(response.StatusCode == 100){ return; } m_pResponses.Add(response); EnsureDialog(response); PassResponseToDialog(response); // Send unreliably (Pass to tranport layer) m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,response); } // 2xx response else if(response.StausCodeType == SIP_StatusCodeType.Success){ EnsureDialog(response); PassResponseToDialog(response); // Send unreliably (Pass to tranport layer) m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,response); m_TransactionState = SIP_ServerTransactionState.Terminated; Dispose(); } // 300-699 response else{ m_TransactionState = SIP_ServerTransactionState.Completed; m_pResponses.Add(response); // Send unreliably (Pass to tranport layer) m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,response); /* RFC 3261 17.2.1. When the "Completed" state is entered, timer H MUST be set to fire in 64*T1 seconds for all transports. Timer H determines when the server transaction abandons retransmitting the response. */ m_pTimerH = new Timer(64 * m_T1); m_pTimerH.AutoReset = false; m_pTimerH.Elapsed += new ElapsedEventHandler(m_pTimerH_Elapsed); // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) Timer H(response retransmit timeout timer) started, will triger after " + m_pTimerH.Interval + "."); /* RFC 3261 17.2.1. For unreliable transports, timer G is set to fire in T1 seconds, and is not set to fire for reliable transports. (INVITE final response retransmit timer) */ if(response.Via.GetTopMostValue().ProtocolTransport.ToUpper() == "UDP"){ m_pTimerG = new Timer(m_T1); m_pTimerG.Elapsed += new ElapsedEventHandler(m_pTimerG_Elapsed); m_pTimerG.Enabled = true; // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) Timer G(INVITE final response retransmit timer) started, will triger after " + m_pTimerG.Interval + "."); } } } #endregion #region Completed // Completed state else if(m_TransactionState == SIP_ServerTransactionState.Completed){ // We do nothing here, we just wait ACK to arrive. } #endregion #region Confirmed else if(m_TransactionState == SIP_ServerTransactionState.Confirmed){ // We do nothing, just wait ACK retransmissions while linger time. } #endregion #region Terminated else if(m_TransactionState == SIP_ServerTransactionState.Terminated){ // We should never rreach here, but if so, skip it. } #endregion } #endregion #region Non-INVITE // Non-INVITE server transaction else{ #region Trying // Trying state if(m_TransactionState == SIP_ServerTransactionState.Trying){ // 1xx response if(response.StausCodeType == SIP_StatusCodeType.Provisional){ m_TransactionState = SIP_ServerTransactionState.Proceeding; m_pResponses.Add(response); // Send unreliably (Pass to tranport layer) m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,response); } // 200-699 response else{ m_TransactionState = SIP_ServerTransactionState.Completed; m_pResponses.Add(response); // Send unreliably (Pass to tranport layer) m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,response); /* RFC 3261 17.2.2. When the server transaction enters the "Completed" state, it MUST set Timer J to fire in 64*T1 seconds for unreliable transports. (wait time for retransmissions of non-INVITE requests) */ if(response.Via.GetTopMostValue().ProtocolTransport.ToUpper() == "UDP"){ m_pTimerJ = new Timer(64 * m_T1); m_pTimerJ.AutoReset = false; m_pTimerJ.Elapsed += new ElapsedEventHandler(m_pTimerJ_Elapsed); m_pTimerJ.Enabled = true; // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) Timer J(wait time for retransmissions of non-INVITE requests) started, will triger after " + m_pTimerJ.Interval + "."); } else{ Dispose(); } } } #endregion #region Proceeding // Proceeding state else if(m_TransactionState == SIP_ServerTransactionState.Proceeding){ // 1xx response if(response.StausCodeType == SIP_StatusCodeType.Provisional){ m_pResponses.Add(response); // Send unreliably (Pass to tranport layer) m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,response); } // 200-699 response else{ m_TransactionState = SIP_ServerTransactionState.Completed; m_pResponses.Add(response); // Send unreliably (Pass to tranport layer) m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,response); /* RFC 3261 17.2.2. When the server transaction enters the "Completed" state, it MUST set Timer J to fire in 64*T1 seconds for unreliable transports. (wait time for retransmissions of non-INVITE requests) */ if(response.Via.GetTopMostValue().ProtocolTransport.ToUpper() == "UDP"){ m_pTimerJ = new Timer(64 * m_T1); m_pTimerJ.AutoReset = false; m_pTimerJ.Elapsed += new ElapsedEventHandler(m_pTimerJ_Elapsed); m_pTimerJ.Enabled = true; // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) Timer J(wait time for retransmissions of non-INVITE requests) started, will triger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Completed // Completed state else if(m_TransactionState == SIP_ServerTransactionState.Completed){ // Just resend last response. m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,GetFinalResponse()); } #endregion #region Terminated // Terminated state else if(m_TransactionState == SIP_ServerTransactionState.Terminated){ // We should never rreach here, but if so, skip it. } #endregion } #endregion }
/// <summary> /// Disposes transaction and cleans up all resources. /// </summary> public override void Dispose() { if(m_Disposed){ return; } try{ m_TransactionState = SIP_ServerTransactionState.Terminated; base.Dispose(); if(m_pTransactionTimeoutTimer != null){ m_pTransactionTimeoutTimer.Dispose(); m_pTransactionTimeoutTimer = null; } if(m_pTimerG != null){ m_pTimerG.Dispose(); m_pTimerG = null; } if(m_pTimerH != null){ m_pTimerH.Dispose(); m_pTimerH = null; } if(m_pTimerI != null){ m_pTimerI.Dispose(); m_pTimerI = null; } if(m_pTimerJ != null){ m_pTimerJ.Dispose(); m_pTimerJ = null; } // Log m_pSipStack.Logger.AddDebug("Transaction(id='" + m_ID + "' method=" + m_pRequest.Method + " server=true) disposed."); } finally{ // Remove from transactions collection. m_pSipStack.TransactionLayer.RemoveServerTransaction(this); OnTerminated(); } m_Disposed = true; }