Click here to Skip to main content
15,881,600 members
Articles / Web Development / HTML

My own Mailinator in 5 minutes

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
19 Nov 2012CPOL6 min read 31.6K   405   13  
A simple Mailinator clone developed in five minutes with the NetFluid framework
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using LumiSoft.Net.SIP.Message;

namespace LumiSoft.Net.SIP.Stack
{
    /// <summary>
    /// Implements SIP client transaction. Defined in RFC 3261 17.1.
    /// </summary>
    public class SIP_ClientTransaction : SIP_Transaction
    {
        private TimerEx m_pTimerA     = null;
        private TimerEx m_pTimerB     = null;
        private TimerEx m_pTimerD     = null;
        private TimerEx m_pTimerE     = null;
        private TimerEx m_pTimerF     = null;
        private TimerEx m_pTimerK     = null;
        private TimerEx m_pTimerM     = null;
        private bool    m_IsCanceling = false;
        
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="stack">Owner SIP stack.</param>
        /// <param name="flow">SIP data flow which is used to send request.</param>
        /// <param name="request">SIP request that transaction will handle.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>flow</b> or <b>request</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        internal SIP_ClientTransaction(SIP_Stack stack,SIP_Flow flow,SIP_Request request) : base(stack,flow,request)
        {
            // Log
            if(this.Stack.Logger != null){
                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] created.");
            }
           
            SetState(SIP_TransactionState.WaitingToStart);            
        }

        #region method Dispose

        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public override void Dispose()
        {
            lock(this.SyncRoot){
                if(this.IsDisposed){
                    return;
                }

                // Log
                if(this.Stack.Logger != null){
                    this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] disposed.");
                }                               
                
                // Kill timers.
                if(m_pTimerA != null){
                    m_pTimerA.Dispose();
                    m_pTimerA = null;
                }
                if(m_pTimerB != null){
                    m_pTimerB.Dispose();
                    m_pTimerB = null;
                }
                if(m_pTimerD != null){
                    m_pTimerD.Dispose();
                    m_pTimerD = null;
                }
                if(m_pTimerE != null){
                    m_pTimerE.Dispose();
                    m_pTimerE = null;
                }
                if(m_pTimerF != null){
                    m_pTimerF.Dispose();
                    m_pTimerF = null;
                }
                if(m_pTimerK != null){
                    m_pTimerK.Dispose();
                    m_pTimerK = null;
                }
                if(m_pTimerM != null){
                    m_pTimerM.Dispose();
                    m_pTimerM = null;
                }

                this.ResponseReceived = null;

                base.Dispose();
            }
        }

        #endregion


        #region Events handling

        #region method m_pTimerA_Elapsed

        /// <summary>
        /// Is raised when INVITE timer A triggered.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pTimerA_Elapsed(object sender,System.Timers.ElapsedEventArgs e)
        {
            /* RFC 3261 17.1.1.2.
                When timer A fires, the client transaction MUST retransmit the
                request by passing it to the transport layer, and MUST reset the
                timer with a value of 2*T1.  The formal definition of retransmit
                within the context of the transaction layer is to take the message
                previously sent to the transport layer and pass it to the transport
                layer once more.
             
                When timer A fires 2*T1 seconds later, the request MUST be
                retransmitted again (assuming the client transaction is still in this
                state).  This process MUST continue so that the request is
                retransmitted with intervals that double after each transmission.
                These retransmissions SHOULD only be done while the client
                transaction is in the "calling" state.
            */

            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Calling){
                    // Log
                    if(this.Stack.Logger != null){
                        this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) triggered.");
                    }

                    try{
                        // Retransmit request.
                        this.Stack.TransportLayer.SendRequest(this.Flow,this.Request,this);
                    }
                    catch(Exception x){
                        OnTransportError(x);
                        SetState(SIP_TransactionState.Terminated);
                        return;
                    }

                    // Update(double current) next transmit time.
                    m_pTimerA.Interval *= 2;
                    m_pTimerA.Enabled = true;

                    // Log
                    if(this.Stack.Logger != null){
                        this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) updated, will trigger after " + m_pTimerA.Interval + ".");
                    }
                }
            }
        }

        #endregion

        #region method m_pTimerB_Elapsed

        /// <summary>
        /// Is raised when INVITE timer B triggered.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pTimerB_Elapsed(object sender,System.Timers.ElapsedEventArgs e)
        {
            /* RFC 3261 17.1.1.2.
                If the client transaction is still in the "Calling" state when timer
                B fires, the client transaction SHOULD inform the TU that a timeout
                has occurred.  The client transaction MUST NOT generate an ACK.  The
                value of 64*T1 is equal to the amount of time required to send seven
                requests in the case of an unreliable transport.
            */

            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Calling){
                    // Log
                    if(this.Stack.Logger != null){
                        this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer B(INVITE calling state timeout) triggered.");
                    }

                    OnTimedOut();
                    SetState(SIP_TransactionState.Terminated);

                    // Stop timers A,B.
                    if(m_pTimerA != null){
                        m_pTimerA.Dispose();
                        m_pTimerA = null;
                    }
                    if(m_pTimerB != null){
                        m_pTimerB.Dispose();
                        m_pTimerB = null;
                    }                   
                }
            }
        }

        #endregion

        #region method m_pTimerD_Elapsed

        /// <summary>
        /// Is raised when INVITE timer D triggered.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pTimerD_Elapsed(object sender,System.Timers.ElapsedEventArgs e)
        {
            /* RFC 3261 17.1.1.2.
                If timer D fires while the client transaction is in the "Completed"
                state, the client transaction MUST move to the terminated state.
            */

            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Completed){
                    // Log
                    if(this.Stack.Logger != null){
                        this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) triggered.");
                    }

                    SetState(SIP_TransactionState.Terminated);
                }
            }
        }

        #endregion

        #region method m_pTimerE_Elapsed

        /// <summary>
        /// Is raised when Non-INVITE timer E triggered.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pTimerE_Elapsed(object sender,System.Timers.ElapsedEventArgs e)
        {
            /* RFC 3261 17.1.2.2.
                If timer E fires while in Trying state, the timer is reset, but this time with a value of MIN(2*T1, T2).
                When the timer fires again, it is reset to a MIN(4*T1, T2). This process continues so that 
                retransmissions occur with an exponentially increasing interval that caps at T2. The default 
                value of T2 is 4s, and it represents the amount of time a non-INVITE server transaction will take to
                respond to a request, if it does not respond immediately. For the default values of T1 and T2, 
                this results in intervals of 500 ms, 1 s, 2 s, 4 s, 4 s, 4 s, etc.
            */

            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Trying){
                    // Log
                    if(this.Stack.Logger != null){
                        this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(-NonINVITE request retransmission) triggered.");
                    }

                    try{
                        // Retransmit request.
                        this.Stack.TransportLayer.SendRequest(this.Flow,this.Request,this);
                    }
                    catch(Exception x){
                        OnTransportError(x);
                        SetState(SIP_TransactionState.Terminated);
                        return;
                    }

                    // Update(double current) next transmit time.
                    m_pTimerE.Interval = Math.Min(m_pTimerE.Interval * 2,SIP_TimerConstants.T2);
                    m_pTimerE.Enabled = true;

                    // Log
                    if(this.Stack.Logger != null){
                        this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(Non-INVITE request retransmission) updated, will trigger after " + m_pTimerE.Interval + ".");
                    }
                }
            }
        }

        #endregion

        #region method m_pTimerF_Elapsed

        /// <summary>
        /// Is raised when Non-INVITE timer F triggered.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pTimerF_Elapsed(object sender,System.Timers.ElapsedEventArgs e)
        {
            /* RFC 3261 17.1.2.2.
                If Timer F fires while in the "Trying" state, the client transaction SHOULD inform the TU about the
                timeout, and then it SHOULD enter the "Terminated" state.
            
                If timer F fires while in the "Proceeding" state, the TU MUST be informed of a timeout, and the
                client transaction MUST transition to the terminated state.
            */

            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Trying || this.State == SIP_TransactionState.Proceeding){
                    // Log
                    if(this.Stack.Logger != null){
                        this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) triggered.");
                    }

                    OnTimedOut();

                    // User Disposed this transaction in TimedOut
                    if(this.State == SIP_TransactionState.Disposed){
                        // Do nothing.
                    }
                    // Switch to terminated state.
                    else{
                        SetState(SIP_TransactionState.Terminated);

                        if(m_pTimerE != null){
                             m_pTimerE.Dispose();
                            m_pTimerE = null;
                        }
                        if(m_pTimerF != null){
                             m_pTimerF.Dispose();
                            m_pTimerF = null;
                        }
                    }
                }
            }
        }

        #endregion

        #region method m_pTimerK_Elapsed

        /// <summary>
        /// Is raised when Non-INVITE timer K triggered.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pTimerK_Elapsed(object sender,System.Timers.ElapsedEventArgs e)
        {
            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Completed){
                    // Log
                    if(this.Stack.Logger != null){
                        this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) triggered.");
                    }

                    SetState(SIP_TransactionState.Terminated);
                }
            }
        }

        #endregion

        #region method m_pTimerM_Elapsed

        /// <summary>
        /// Is called when INVITE timer M triggered.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pTimerM_Elapsed(object sender,System.Timers.ElapsedEventArgs e)
        {
            /* RFC 6026 7.2.
                When Timer M fires and the state machine is in the "Accepted" state,
                the machine MUST transition to the "Terminated" state.
            */

            lock(this.SyncRoot){
                // Log
                if(this.Stack.Logger != null){
                    this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer M(2xx response retransmission wait) triggered.");
                }

                SetState(SIP_TransactionState.Terminated);                
            }
        }

        #endregion

        #endregion


        #region method Start

        /// <summary>
        /// Starts transaction processing.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when <b>Start</b> is called other state than 'WaitingToStart'.</exception>
        public void Start()
        {   
            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Disposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                else if(this.State != SIP_TransactionState.WaitingToStart){
                    throw new InvalidOperationException("Start method is valid only in 'WaitingToStart' state.");
                }

                // Move processing to thread pool.
                ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object state){
                    lock(this.SyncRoot){
                        #region INVITE

                        if(this.Method == SIP_Methods.INVITE){
                            /* RFC 3261 17.1.1.2.
                                The initial state, "calling", MUST be entered when the TU
                                initiates a new client transaction with an INVITE request.  The
                                client transaction MUST pass the request to the transport layer for
                                transmission (see Section 18).  If an unreliable transport is being
                                used, the client transaction MUST start timer A with a value of T1.
                                If a reliable transport is being used, the client transaction SHOULD
                                NOT start timer A (Timer A controls request retransmissions).  For
                                any transport, the client transaction MUST start timer B with a value
                                of 64*T1 seconds (Timer B controls transaction timeouts).
                            */

                            SetState(SIP_TransactionState.Calling);

                            try{
                                // Send initial request.
                                this.Stack.TransportLayer.SendRequest(this.Flow,this.Request,this);
                            }
                            catch(Exception x){
                                OnTransportError(x);
                                // NOTE: TransportError event handler could Dispose this transaction, so we need to check it.
                                if(this.State != SIP_TransactionState.Disposed){
                                    SetState(SIP_TransactionState.Terminated);
                                }
                                return;
                            }
                            
                            // Start timer A for unreliable transports.
                            if(!this.Flow.IsReliable){
                                m_pTimerA = new TimerEx(SIP_TimerConstants.T1,false);
                                m_pTimerA.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerA_Elapsed);
                                m_pTimerA.Enabled = true;

                                // Log
                                if(this.Stack.Logger != null){
                                    this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) started, will trigger after " + m_pTimerA.Interval + ".");
                                }
                            }

                            // Start timer B.
                            m_pTimerB = new TimerEx(64 * SIP_TimerConstants.T1,false);
                            m_pTimerB.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerB_Elapsed);
                            m_pTimerB.Enabled = true;

                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer B(INVITE calling state timeout) started, will trigger after " + m_pTimerB.Interval + ".");
                            }
                        }

                        #endregion

                        #region Non-INVITE

                        else{
                            /* RFC 3261 17.1.2.2.
                                The "Trying" state is entered when the TU initiates a new client
                                transaction with a request.  When entering this state, the client
                                transaction SHOULD set timer F to fire in 64*T1 seconds.  The request
                                MUST be passed to the transport layer for transmission.  If an
                                unreliable transport is in use, the client transaction MUST set timer
                                E to fire in T1 seconds.
                            */

                            SetState(SIP_TransactionState.Trying);

                            // Start timer F.
                            m_pTimerF = new TimerEx(64 * SIP_TimerConstants.T1,false);
                            m_pTimerF.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerF_Elapsed);
                            m_pTimerF.Enabled = true;

                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) started, will trigger after " + m_pTimerF.Interval + ".");
                            }

                            try{
                                // Send initial request.
                                this.Stack.TransportLayer.SendRequest(this.Flow,this.Request,this);                                 
                            }
                            catch(Exception x){
                                OnTransportError(x);
                                // NOTE: TransportError event handler could Dispose this transaction, so we need to check it.
                                if(this.State != SIP_TransactionState.Disposed){
                                    SetState(SIP_TransactionState.Terminated);
                                }
                                return;
                            }

                            // Start timer E for unreliable transports.
                            if(!this.Flow.IsReliable){ 
                                m_pTimerE = new TimerEx(SIP_TimerConstants.T1,false);
                                m_pTimerE.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerE_Elapsed);
                                m_pTimerE.Enabled = true;

                                // Log
                                if(this.Stack.Logger != null){
                                    this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(Non-INVITE request retransmission) started, will trigger after " + m_pTimerE.Interval + ".");
                                }
                            }
                        }

                        #endregion
                    }
                }));                
            }
        }
                                
        #endregion

        #region method Cancel

        /// <summary>
        /// Starts canceling transaction. 
        /// </summary>
        /// <remarks>If client transaction has got final response, canel has no effect and will be ignored.</remarks>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        public override void Cancel()
        {
            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Disposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                else if(this.State == SIP_TransactionState.WaitingToStart){
                    SetState(SIP_TransactionState.Terminated);
                    return;
                }
                else if(m_IsCanceling){
                    return;
                }
                else if(this.State == SIP_TransactionState.Terminated){
                    // RFC 3261 9.1. We got final response, nothing to cancel.
                    return;
                }
                if(this.FinalResponse != null){
                    return;
                }

                m_IsCanceling = true;

                /* RFC 3261 9.1.
                    If no provisional response has been received, the CANCEL request MUST
                    NOT be sent; rather, the client MUST wait for the arrival of a
                    provisional response before sending the request.
                */
                if(this.Responses.Length == 0){
                    // We set canceling flag, so if provisional response arrives, we do cancel.
                }
                else{
                    SendCancel();
                }
            }
        }

        #endregion


        #region method ProcessResponse

        /// <summary>
        /// Processes specified response through this transaction.
        /// </summary>
        /// <param name="flow">SIP data flow what received response.</param>
        /// <param name="response">SIP response to process.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b>,<b>response</b> is null reference.</exception>
        internal void ProcessResponse(SIP_Flow flow,SIP_Response response)
        {
            if(flow == null){
                throw new ArgumentNullException("flow");
            }
            if(response == null){
                throw new ArgumentNullException("response");
            }

            lock(this.SyncRoot){
                if(this.State == SIP_TransactionState.Disposed){
                    return;
                }
                /* RFC 3261 9.1. CANCEL.
                    *) If provisional response, send CANCEL, we should get '478 Request terminated'.
                    *) If final response, skip canceling, nothing to cancel.
                */
                else if(m_IsCanceling && response.StatusCodeType == SIP_StatusCodeType.Provisional){
                    SendCancel();
                    return;
                }

                // Log
                if(this.Stack.Logger != null){
                    byte[] responseData = response.ToByteData();

                    this.Stack.Logger.AddRead(
                        Guid.NewGuid().ToString(),
                        null,
                        0,
                        "Response [transactionID='" +  this.ID + "'; method='" + response.CSeq.RequestMethod + "'; cseq='" + response.CSeq.SequenceNumber + "'; " + 
                        "transport='" + flow.Transport + "'; size='" + responseData.Length + "'; statusCode='" + response.StatusCode + "'; " + 
                        "reason='" + response.ReasonPhrase + "'; received '" + flow.LocalEP + "' <- '" + flow.RemoteEP + "'.",
                        flow.LocalEP,
                        flow.RemoteEP,
                        responseData
                    );
                }


                #region INVITE

                /* RFC 6026 7.2. INVITE client transaction. (Udpates RFC 3261)
                      +-----------+                        +-----------+
                      |           |                        |           |
                      |  Calling  |                        |  Calling  |
                      |           |----------->+           |           |-----------+
                      +-----------+ 2xx        |           +-----------+ 2xx       |
                                    2xx to TU  |                         2xx to TU |
                                               |                                   |
                                               |                                   |
                                               |                                   |
                                               |                                   |
                      +-----------+            |           +-----------+           |
                      |           |            |           |           |           |
                      |Proceeding |----------->|           |Proceeding |---------->|
                      |           | 2xx        |           |           | 2xx       |
                      +-----------+ 2xx to TU  |           +-----------+ 2xx to TU |
                                               |                                   |
                                               |                                   |
                                               |                                   |
                                               |                                   V
                                               |                            +-----------+
                                               |                            |           |
                                               |                            | Accepted  |
                                               |                        +---|           |
                                               |              2xx       |   +-----------+
                                               |              2xx to TU |     ^    |
                                               |                        |     |    |
                                               |                        +-----+    |
                                               |                                   |
                                               |                 +-----------------+
                                               |                 | Timer M fires
                                               |                 | -
                                               |                 V
                      +-----------+            |           +-----------+
                      |           |            |           |           |
                      | Terminated|<-----------+           | Terminated|
                      |           |                        |           |
                      +-----------+                        +-----------+


                */

                if(this.Method == SIP_Methods.INVITE){
                    #region Calling

                    if(this.State == SIP_TransactionState.Calling){
                        // Store response.
                        AddResponse(response);

                        // Stop timer A,B
                        if(m_pTimerA != null){
                            m_pTimerA.Dispose();
                            m_pTimerA = null;

                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) stopped.");
                            }
                        }
                        if(m_pTimerB != null){
                            m_pTimerB.Dispose();
                            m_pTimerB = null;

                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer B(INVITE calling state timeout) stopped.");
                            }
                        }

                        // 1xx response.
                        if(response.StatusCodeType == SIP_StatusCodeType.Provisional){
                            OnResponseReceived(response);
                            SetState(SIP_TransactionState.Proceeding);
                        }
                        // 2xx response.
                        else if(response.StatusCodeType == SIP_StatusCodeType.Success){
                            OnResponseReceived(response);
                            SetState(SIP_TransactionState.Accpeted);

                            /* RFC 6025 7.1.
                                When the "Accepted" state is entered, timer L MUST be set to fire in 64*T1.
                            */
                            m_pTimerM = new TimerEx(64 * SIP_TimerConstants.T1);
                            m_pTimerM.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerM_Elapsed);
                            m_pTimerM.Enabled = true;

                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer M(2xx retransmission wait) started, will trigger after " + m_pTimerM.Interval + ".");
                            }
                        }
                        // 3xx - 6xx response.
                        else{
                            SendAck(response);
                            OnResponseReceived(response);
                            SetState(SIP_TransactionState.Completed);

                            /* RFC 3261 17.1.1.2. 
                                The client transaction SHOULD start timer D when it enters the "Completed" state, 
                                with a value of at least 32 seconds for unreliable transports, and a value of zero 
                                seconds for reliable transports.
                            */
                            m_pTimerD = new TimerEx(this.Flow.IsReliable ? 0 : 32000,false);
                            m_pTimerD.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerD_Elapsed);
                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerD.Interval + ".");
                            }
                            m_pTimerD.Enabled = true;
                        }
                    }

                    #endregion

                    #region Proceeding

                    else if(this.State == SIP_TransactionState.Proceeding){
                        // Store response.
                        AddResponse(response);

                        // 1xx response.
                        if(response.StatusCodeType == SIP_StatusCodeType.Provisional){
                            OnResponseReceived(response);
                        }
                        // 2xx response.
                        else if(response.StatusCodeType == SIP_StatusCodeType.Success){
                            OnResponseReceived(response);
                            SetState(SIP_TransactionState.Accpeted);

                            /* RFC 6025 7.1.
                                When the "Accepted" state is entered, timer L MUST be set to fire in 64*T1.
                            */
                            m_pTimerM = new TimerEx(64 * SIP_TimerConstants.T1);
                            m_pTimerM.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerM_Elapsed);
                            m_pTimerM.Enabled = true;

                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer M(2xx retransmission wait) started, will trigger after " + m_pTimerM.Interval + ".");
                            }
                        }
                        // 3xx - 6xx response.
                        else{
                            SendAck(response);
                            OnResponseReceived(response);
                            SetState(SIP_TransactionState.Completed);

                            /* RFC 3261 17.1.1.2. 
                                The client transaction SHOULD start timer D when it enters the "Completed" state, 
                                with a value of at least 32 seconds for unreliable transports, and a value of zero 
                                seconds for reliable transports.
                            */
                            m_pTimerD = new TimerEx(this.Flow.IsReliable ? 0 : 32000,false);
                            m_pTimerD.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerD_Elapsed);
                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerD.Interval + ".");
                            }
                            m_pTimerD.Enabled = true;
                        }
                    }

                    #endregion

                    #region Accepted

                    else if(this.State == SIP_TransactionState.Accpeted){
                        if(response.StatusCodeType == SIP_StatusCodeType.Success){
                            OnResponseReceived(response);
                        }
                    }

                    #endregion

                    #region Completed

                    else if(this.State == SIP_TransactionState.Completed){
                        // 3xx - 6xx
                        if(response.StatusCode >= 300){
                            SendAck(response);
                        }
                    }

                    #endregion

                    #region Terminated

                    else if(this.State == SIP_TransactionState.Terminated){
                        // We should never reach here, but if so, do nothing.
                    }

                    #endregion
                }

                #endregion

                #region Non-INVITE

                /* RFC 3251 17.1.2.2
                                               |Request from TU
                                               |send request
                           Timer E             V
                           send request  +-----------+
                               +---------|           |-------------------+
                               |         |  Trying   |  Timer F          |
                               +-------->|           |  or Transport Err.|
                                         +-----------+  inform TU        |
                            200-699         |  |                         |
                            resp. to TU     |  |1xx                      |
                            +---------------+  |resp. to TU              |
                            |                  |                         |
                            |   Timer E        V       Timer F           |
                            |   send req +-----------+ or Transport Err. |
                            |  +---------|           | inform TU         |
                            |  |         |Proceeding |------------------>|
                            |  +-------->|           |-----+             |
                            |            +-----------+     |1xx          |
                            |              |      ^        |resp to TU   |
                            | 200-699      |      +--------+             |
                            | resp. to TU  |                             |
                            |              |                             |
                            |              V                             |
                            |            +-----------+                   |
                            |            |           |                   |
                            |            | Completed |                   |
                            |            |           |                   |
                            |            +-----------+                   |
                            |              ^   |                         |
                            |              |   | Timer K                 |
                            +--------------+   | -                       |
                                               |                         |
                                               V                         |
                         NOTE:           +-----------+                   |
                                         |           |                   |
                     transitions         | Terminated|<------------------+
                     labeled with        |           |
                     the event           +-----------+
                     over the action
                     to take
                */

                else{
                    #region Trying

                    if(this.State == SIP_TransactionState.Trying){
                        // Store response.
                        AddResponse(response);

                        // Stop timer E
                        if(m_pTimerE != null){
                            m_pTimerE.Dispose();
                            m_pTimerE = null;

                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(Non-INVITE request retransmission) stopped.");
                            }
                        }

                        // 1xx response.
                        if(response.StatusCodeType == SIP_StatusCodeType.Provisional){
                            OnResponseReceived(response);
                            SetState(SIP_TransactionState.Proceeding);
                        }
                        // 2xx - 6xx response.
                        else{
                            // Stop timer F
                            if(m_pTimerF != null){
                                m_pTimerF.Dispose();
                                m_pTimerF = null;

                                // Log
                                if(this.Stack.Logger != null){
                                    this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stopped.");
                                }
                            }

                            OnResponseReceived(response);
                            SetState(SIP_TransactionState.Completed);

                            /* RFC 3261 17.1.2.2. 
                                The client transaction enters the "Completed" state, it MUST set
                                Timer K to fire in T4 seconds for unreliable transports, and zero
                                seconds for reliable transports.
                            */
                            m_pTimerK = new TimerEx(this.Flow.IsReliable ? 1 : SIP_TimerConstants.T4,false);
                            m_pTimerK.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerK_Elapsed);
                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerK.Interval + ".");
                            }
                            m_pTimerK.Enabled = true;
                        }
                    }

                    #endregion

                    #region Proceeding

                    else if(this.State == SIP_TransactionState.Proceeding){
                        // Store response.
                        AddResponse(response);

                        // 1xx response.
                        if(response.StatusCodeType == SIP_StatusCodeType.Provisional){
                            OnResponseReceived(response);
                        }                        
                        // 2xx - 6xx response.
                        else{
                            // Stop timer F
                            if(m_pTimerF != null){
                                m_pTimerF.Dispose();
                                m_pTimerF = null;

                                // Log
                                if(this.Stack.Logger != null){
                                    this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stopped.");
                                }
                            }

                            OnResponseReceived(response);
                            SetState(SIP_TransactionState.Completed);

                            /* RFC 3261 17.1.2.2. 
                                The client transaction enters the "Completed" state, it MUST set
                                Timer K to fire in T4 seconds for unreliable transports, and zero
                                seconds for reliable transports.
                            */
                            m_pTimerK = new TimerEx(this.Flow.IsReliable ? 0 : SIP_TimerConstants.T4,false);
                            m_pTimerK.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerK_Elapsed);
                            // Log
                            if(this.Stack.Logger != null){
                                this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerK.Interval + ".");
                            }
                            m_pTimerK.Enabled = true;
                        }
                    }

                    #endregion

                    #region Completed

                    else if(this.State == SIP_TransactionState.Completed){
                        // Eat retransmited response.
                    }

                    #endregion

                    #region Terminated

                    else if(this.State == SIP_TransactionState.Terminated){
                        // We should never reach here, but if so, do nothing.
                    }

                    #endregion
                }

                #endregion
            }
        }
                                                
        #endregion

        #region method SendCancel

        /// <summary>
        /// Creates and send CANCEL request to remote target.
        /// </summary>
        private void SendCancel()
        {
            /* RFC 3261 9.1.
                The following procedures are used to construct a CANCEL request.  The
                Request-URI, Call-ID, To, the numeric part of CSeq, and From header
                fields in the CANCEL request MUST be identical to those in the
                request being cancelled, including tags.  A CANCEL constructed by a
                client MUST have only a single Via header field value matching the
                top Via value in the request being cancelled.  Using the same values
                for these header fields allows the CANCEL to be matched with the
                request it cancels (Section 9.2 indicates how such matching occurs).
                However, the method part of the CSeq header field MUST have a value
                of CANCEL.  This allows it to be identified and processed as a
                transaction in its own right (See Section 17).

                If the request being cancelled contains a Route header field, the
                CANCEL request MUST include that Route header field's values.

                    This is needed so that stateless proxies are able to route CANCEL
                    requests properly.
            */

            SIP_Request cancelRequest = new SIP_Request(SIP_Methods.CANCEL);
            cancelRequest.RequestLine.Uri = this.Request.RequestLine.Uri;
            cancelRequest.Via.Add(this.Request.Via.GetTopMostValue().ToStringValue());
            cancelRequest.CallID = this.Request.CallID;
            cancelRequest.From = this.Request.From;
            cancelRequest.To = this.Request.To;
            cancelRequest.CSeq = new SIP_t_CSeq(this.Request.CSeq.SequenceNumber,SIP_Methods.CANCEL);
            foreach(SIP_t_AddressParam route in this.Request.Route.GetAllValues()){
                cancelRequest.Route.Add(route.ToStringValue());
            }
            cancelRequest.MaxForwards = 70;

            // We must use same data flow to send CANCEL what sent initial request.
            SIP_ClientTransaction transaction = this.Stack.TransactionLayer.CreateClientTransaction(this.Flow,cancelRequest,false);
            transaction.Start();
        }

        #endregion

        #region method SendAck

        /// <summary>
        /// Creates and sends ACK for final(3xx - 6xx) failure response.
        /// </summary>
        /// <param name="response">SIP response.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null.</exception>
        private void SendAck(SIP_Response response)
        {
            if(response == null){
                throw new ArgumentNullException("resposne");
            }

            /* RFC 3261 17.1.1.3 Construction of the ACK Request.
                The ACK request constructed by the client transaction MUST contain
                values for the Call-ID, From, and Request-URI that are equal to the
                values of those header fields in the request passed to the transport
                by the client transaction (call this the "original request").  The To
                header field in the ACK MUST equal the To header field in the
                response being acknowledged, and therefore will usually differ from
                the To header field in the original request by the addition of the
                tag parameter.  The ACK MUST contain a single Via header field, and
                this MUST be equal to the top Via header field of the original
                request.  The CSeq header field in the ACK MUST contain the same
                value for the sequence number as was present in the original request,
                but the method parameter MUST be equal to "ACK".
              
                If the INVITE request whose response is being acknowledged had Route
                header fields, those header fields MUST appear in the ACK.  This is
                to ensure that the ACK can be routed properly through any downstream
                stateless proxies.
            */

            SIP_Request ackRequest = new SIP_Request(SIP_Methods.ACK);
            ackRequest.RequestLine.Uri = this.Request.RequestLine.Uri;
            ackRequest.Via.AddToTop(this.Request.Via.GetTopMostValue().ToStringValue());
            ackRequest.CallID = this.Request.CallID;
            ackRequest.From = this.Request.From;
            ackRequest.To = response.To;
            ackRequest.CSeq = new SIP_t_CSeq(this.Request.CSeq.SequenceNumber,"ACK");
            foreach(SIP_HeaderField h in response.Header.Get("Route:")){
                ackRequest.Header.Add("Route:",h.Value);
            }
            ackRequest.MaxForwards = 70;

            try{
                // Send request to target.
                this.Stack.TransportLayer.SendRequest(this.Flow,ackRequest,this);
            }
            catch(SIP_TransportException x){
                OnTransportError(x);
                SetState(SIP_TransactionState.Terminated);
            }
        }

        #endregion


        #region Properties implementation
                
        // FIX ME:
        int m_RSeq = -1;

        /// <summary>
        /// Gets or sets RSeq value. Value -1 means no reliable provisional response received.
        /// </summary>
        internal int RSeq
        {
            get{ return m_RSeq; }

            set{ m_RSeq = value; }
        }
 
        #endregion

        #region Events implementation

        /// <summary>
        /// Is raised when transaction received response from remote party.
        /// </summary>
        public event EventHandler<SIP_ResponseReceivedEventArgs> ResponseReceived = null;

        #region method OnResponseReceived

        /// <summary>
        /// Raises ResponseReceived event.
        /// </summary>
        /// <param name="response">SIP response received.</param>
        private void OnResponseReceived(SIP_Response response)
        {
            if(this.ResponseReceived != null){
                this.ResponseReceived(this,new SIP_ResponseReceivedEventArgs(this.Stack,this,response));
            }
        }

        #endregion

        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Genny Mobility
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions