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

            #region Contact (RFC 3261 8.1.1.8)

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

            SIP_t_ContactParam contact = request.Contact.GetTopMostValue();

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

                request.Contact.Add((flow.IsSecure ? "sips:" : "sip:") + from.User + "@" + flow.LocalPublicEP.ToString());

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

                // REMOVE ME: 22.10.2010
                //((SIP_Uri)contact.Address.Uri).Host =  m_pStack.TransportLayer.GetContactHost(flow).ToString();
            }

            #endregion

            m_pTransaction = m_pStack.TransactionLayer.CreateClientTransaction(flow, request, true);
            m_pTransaction.ResponseReceived += new EventHandler <SIP_ResponseReceivedEventArgs>(ClientTransaction_ResponseReceived);
            m_pTransaction.TimedOut         += new EventHandler(ClientTransaction_TimedOut);
            m_pTransaction.TransportError   += new EventHandler <ExceptionEventArgs>(ClientTransaction_TransportError);

            // Start transaction processing.
            m_pTransaction.Start();
        }
Example #2
0
        /// <summary>
        /// Sends notify request to remote end point.
        /// </summary>
        /// <param name="notify">SIP NOTIFY request.</param>
        public void Notify(SIP_Request notify)
        {
            if (notify == null)
            {
                throw new ArgumentNullException("notify");
            }

            // TODO:
        }
Example #3
0
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="stack">Owner SIP stack.</param>
        /// <param name="flow">Transaction data flow.</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>
        public SIP_Transaction(SIP_Stack stack, SIP_Flow flow, SIP_Request request)
        {
            if (stack == null)
            {
                throw new ArgumentNullException("stack");
            }
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            m_pStack     = stack;
            m_pFlow      = flow;
            m_pRequest   = request;
            m_Method     = request.RequestLine.Method;
            m_CreateTime = DateTime.Now;
            m_pResponses = new List <SIP_Response>();

            // Validate Via:
            SIP_t_ViaParm via = request.Via.GetTopMostValue();

            if (via == null)
            {
                throw new ArgumentException("Via: header is missing !");
            }
            if (via.Branch == null)
            {
                throw new ArgumentException("Via: header 'branch' parameter is missing !");
            }

            m_ID = via.Branch;

            if (this is SIP_ServerTransaction)
            {
                /*
                 *  We use branch and sent-by as indexing key for transaction, the only special what we need to
                 *  do is to handle CANCEL, because it has same branch as transaction to be canceled.
                 *  For avoiding key collision, we add branch + '-' + 'sent-by' + CANCEL for cancel index key.
                 *  ACK has also same branch, but we won't do transaction for ACK, so it isn't problem.
                 */
                string key = request.Via.GetTopMostValue().Branch + '-' + request.Via.GetTopMostValue().SentBy;
                if (request.RequestLine.Method == SIP_Methods.CANCEL)
                {
                    key += "-CANCEL";
                }
                m_Key = key;
            }
            else
            {
                m_Key = m_ID + "-" + request.RequestLine.Method;
            }
        }
Example #4
0
        /// <summary>
        /// Clones this request.
        /// </summary>
        /// <returns>Returns new cloned request.</returns>
        public SIP_Request Copy()
        {
            SIP_Request retVal = SIP_Request.Parse(this.ToByteData());

            retVal.Flow           = m_pFlow;
            retVal.LocalEndPoint  = m_pLocalEP;
            retVal.RemoteEndPoint = m_pRemoteEP;

            return(retVal);
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="stack">Owner SIP stack.</param>
        /// <param name="flow">SIP data flow which received 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>
        public SIP_ServerTransaction(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=true] created.");
            }

            Start();
        }
Example #6
0
        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public virtual void Dispose()
        {
            SetState(SIP_TransactionState.Disposed);
            OnDisposed();

            m_pStack   = null;
            m_pFlow    = null;
            m_pRequest = null;

            this.StateChanged   = null;
            this.Disposed       = null;
            this.TimedOut       = null;
            this.TransportError = null;
        }
        /// <summary>
        /// Creates new client transaction.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to send request.</param>
        /// <param name="request">SIP request that transaction will handle.</param>
        /// <param name="addVia">If true, transaction will add <b>Via:</b> header, otherwise it's user responsibility.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        /// <returns>Returns created transaction.</returns>
        public SIP_ClientTransaction CreateClientTransaction(SIP_Flow flow, SIP_Request request, bool addVia)
        {
            if (m_IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

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

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

                SIP_Dialog dialog = MatchDialog(request);
                if (dialog != null)
                {
                    dialog.AddTransaction(transaction);
                }

                return(transaction);
            }
        }
Example #8
0
        /// <summary>
        /// Sends specified request to flow remote end point.
        /// </summary>
        /// <param name="request">SIP request to send.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>request</b> is null reference.</exception>
        public void Send(SIP_Request request)
        {
            lock (m_pLock){
                if (m_IsDisposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                if (request == null)
                {
                    throw new ArgumentNullException("request");
                }

                SendInternal(request.ToByteData());
            }
        }
        /// <summary>
        /// Matches SIP request to server transaction. If not matching transaction found, returns null.
        /// </summary>
        /// <param name="request">SIP request to match.</param>
        /// <returns>Returns matching transaction or null if no match.</returns>
        internal SIP_ServerTransaction MatchServerTransaction(SIP_Request request)
        {
            /* RFC 3261 17.2.3 Matching Requests to Server Transactions.
             *  This matching rule applies to both INVITE and non-INVITE transactions.
             *
             *  1. the branch parameter in the request is equal to the one in the top Via header
             *     field of the request that created the transaction, and
             *
             *  2. the sent-by value in the top Via of the request is equal to the
             *     one in the request that created the transaction, and
             *
             *  3. the method of the request matches the one that created the transaction, except
             *     for ACK, where the method of the request that created the transaction is INVITE.
             *
             *  Internal implementation notes:
             *      Inernally we use branch + '-' + sent-by for non-CANCEL and for CANCEL
             *      branch + '-' + sent-by + '-' CANCEL. This is because method matching is actually
             *      needed for CANCEL only (CANCEL shares cancelable transaction branch ID).
             */

            SIP_ServerTransaction retVal = null;

            /*
             *  We use branch and sent-by as indexing key for transaction, the only special what we need to
             *  do is to handle CANCEL, because it has same branch as transaction to be canceled.
             *  For avoiding key collision, we add branch + '-' + 'sent-by' + CANCEL for cancel index key.
             *  ACK has also same branch, but we won't do transaction for ACK, so it isn't problem.
             */
            string key = request.Via.GetTopMostValue().Branch + '-' + request.Via.GetTopMostValue().SentBy;

            if (request.RequestLine.Method == SIP_Methods.CANCEL)
            {
                key += "-CANCEL";
            }

            lock (m_pServerTransactions){
                m_pServerTransactions.TryGetValue(key, out retVal);
            }

            // Don't match ACK for terminated transaction, in that case ACK must be passed to "core".
            if (retVal != null && request.RequestLine.Method == SIP_Methods.ACK && retVal.State == SIP_TransactionState.Terminated)
            {
                retVal = null;
            }

            return(retVal);
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="stack">Owner stack.</param>
        /// <param name="request">SIP request.</param>
        /// <param name="flow">Active data flow what to try before RFC 3261 [4](RFC 3263) methods to use to send request.
        /// This value can be null.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>stack</b> or <b>request</b> is null.</exception>
        internal SIP_RequestSender(SIP_Stack stack, SIP_Request request, SIP_Flow flow)
        {
            if (stack == null)
            {
                throw new ArgumentNullException("stack");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            m_pStack   = stack;
            m_pRequest = request;
            m_pFlow    = flow;

            m_pCredentials = new List <NetworkCredential>();
            m_pHops        = new Queue <SIP_Hop>();
        }
Example #11
0
        /// <summary>
        /// Creates SIP request sender for the specified request.
        /// </summary>
        /// <remarks>All requests sent through this dialog SHOULD use this request sender to send out requests.</remarks>
        /// <param name="request">SIP request.</param>
        /// <returns>Returns created sender.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>request</b> is null.</exception>
        public SIP_RequestSender CreateRequestSender(SIP_Request request)
        {
            lock (m_pLock){
                if (this.State == SIP_DialogState.Terminated)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                if (request == null)
                {
                    throw new ArgumentNullException("request");
                }

                // TODO: Request sender must use dialog sequence numbering if authentication done.

                SIP_RequestSender sender = m_pStack.CreateRequestSender(request, this.Flow);

                return(sender);
            }
        }
        /// <summary>
        /// Starts unregistering.
        /// </summary>
        /// <param name="dispose">If true, registration will be disposed after unregister.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        public void BeginUnregister(bool dispose)
        {
            if (m_IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }

            m_AutoDispose = dispose;

            // Stop register timer, otherwise we may get register and unregister race condition.
            m_pTimer.Enabled = false;

            if (m_State == SIP_UA_RegistrationState.Registered)
            {
                /* RFC 3261 10.1 Constructing the REGISTER Request.
                 *  Request-URI: The Request-URI names the domain of the location service for which the registration is meant (for example,
                 *               "sip:chicago.com").  The "userinfo" and "@" components of the SIP URI MUST NOT be present.
                 */

                SIP_Request unregister = m_pStack.CreateRequest(SIP_Methods.REGISTER, new SIP_t_NameAddress(m_pServer.Scheme + ":" + m_AOR), new SIP_t_NameAddress(m_pServer.Scheme + ":" + m_AOR));
                unregister.RequestLine.Uri = SIP_Uri.Parse(m_pServer.Scheme + ":" + m_AOR.Substring(m_AOR.IndexOf('@') + 1));
                unregister.Route.Add(m_pServer.ToString());
                unregister.Contact.Add("<" + this.Contact + ">;expires=0");

                m_pUnregisterSender = m_pStack.CreateRequestSender(unregister, m_pFlow);
                m_pUnregisterSender.ResponseReceived += new EventHandler <SIP_ResponseReceivedEventArgs>(m_pUnregisterSender_ResponseReceived);
                m_pUnregisterSender.Start();
            }
            else
            {
                SetState(SIP_UA_RegistrationState.Unregistered);
                OnUnregistered();

                if (m_AutoDispose)
                {
                    Dispose();
                }

                m_pUnregisterSender = null;
            }
        }
        /// <summary>
        /// Ensures that specified request has matching server transaction. If server transaction doesn't exist,
        /// it will be created, otherwise existing transaction will be returned.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to receive request.</param>
        /// <param name="request">SIP request.</param>
        /// <returns>Returns matching transaction.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null.</exception>
        /// <exception cref="InvalidOperationException">Is raised when request.Method is ACK request.</exception>
        public SIP_ServerTransaction EnsureServerTransaction(SIP_Flow flow, SIP_Request request)
        {
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            if (request.RequestLine.Method == SIP_Methods.ACK)
            {
                throw new InvalidOperationException("ACK request is transaction less request, can't create transaction for it.");
            }

            /*
             *  We use branch and sent-by as indexing key for transaction, the only special what we need to
             *  do is to handle CANCEL, because it has same branch as transaction to be canceled.
             *  For avoiding key collision, we add branch + '-' + 'sent-by' + CANCEL for cancel index key.
             *  ACK has also same branch, but we won't do transaction for ACK, so it isn't problem.
             */
            string key = request.Via.GetTopMostValue().Branch + '-' + request.Via.GetTopMostValue().SentBy;

            if (request.RequestLine.Method == SIP_Methods.CANCEL)
            {
                key += "-CANCEL";
            }

            lock (m_pServerTransactions){
                SIP_ServerTransaction retVal = null;
                m_pServerTransactions.TryGetValue(key, out retVal);
                // We don't have transaction, create it.
                if (retVal == null)
                {
                    retVal = CreateServerTransaction(flow, request);
                }

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

                OnDisposed();

                this.ResponseReceived = null;
                this.Completed        = null;
                this.Disposed         = null;

                m_pStack       = null;
                m_pRequest     = null;
                m_pCredentials = null;
                m_pHops        = null;
                m_pTransaction = null;
                m_pLock        = null;
            }
        }
        /// <summary>
        /// Matches CANCEL requst to SIP server non-CANCEL transaction. Returns null if no match.
        /// </summary>
        /// <param name="cancelRequest">SIP CANCEL request.</param>
        /// <returns>Returns CANCEL matching server transaction or null if no match.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>cancelTransaction</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when <b>cancelTransaction</b> has invalid.</exception>
        public SIP_ServerTransaction MatchCancelToTransaction(SIP_Request cancelRequest)
        {
            if (cancelRequest == null)
            {
                throw new ArgumentNullException("cancelRequest");
            }
            if (cancelRequest.RequestLine.Method != SIP_Methods.CANCEL)
            {
                throw new ArgumentException("Argument 'cancelRequest' is not SIP CANCEL request.");
            }

            SIP_ServerTransaction retVal = null;

            // NOTE: There we don't add '-CANCEL' because we want to get CANCEL matching transaction, not CANCEL
            //       transaction itself.
            string key = cancelRequest.Via.GetTopMostValue().Branch + '-' + cancelRequest.Via.GetTopMostValue().SentBy;

            lock (m_pServerTransactions){
                m_pServerTransactions.TryGetValue(key, out retVal);
            }

            return(retVal);
        }
        /// <summary>
        /// Creates new SIP server transaction for specified request.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to receive request.</param>
        /// <param name="request">SIP request.</param>
        /// <returns>Returns added server transaction.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        public SIP_ServerTransaction CreateServerTransaction(SIP_Flow flow, SIP_Request request)
        {
            if (m_IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            lock (m_pServerTransactions){
                SIP_ServerTransaction transaction = new SIP_ServerTransaction(m_pStack, flow, request);
                m_pServerTransactions.Add(transaction.Key, transaction);
                transaction.StateChanged += new EventHandler(delegate(object s, EventArgs e){
                    if (transaction.State == SIP_TransactionState.Terminated)
                    {
                        lock (m_pClientTransactions){
                            m_pServerTransactions.Remove(transaction.Key);
                        }
                    }
                });

                SIP_Dialog dialog = MatchDialog(request);
                if (dialog != null)
                {
                    dialog.AddTransaction(transaction);
                }

                return(transaction);
            }
        }
        /// <summary>
        /// Processes specified request through this transaction.
        /// </summary>
        /// <param name="flow">SIP data flow.</param>
        /// <param name="request">SIP request.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        internal void ProcessRequest(SIP_Flow flow, SIP_Request request)
        {
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            lock (this.SyncRoot){
                if (this.State == SIP_TransactionState.Disposed)
                {
                    return;
                }

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

                        this.Stack.Logger.AddRead(
                            Guid.NewGuid().ToString(),
                            null,
                            0,
                            "Request [transactionID='" + this.ID + "'; method='" + request.RequestLine.Method + "'; cseq='" + request.CSeq.SequenceNumber + "'; " +
                            "transport='" + flow.Transport + "'; size='" + requestData.Length + "'; received '" + flow.LocalEP + "' <- '" + flow.RemoteEP + "'.",
                            flow.LocalEP,
                            flow.RemoteEP,
                            requestData
                            );
                    }

                    #region INVITE

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

                        if (request.RequestLine.Method == SIP_Methods.INVITE)
                        {
                            if (this.State == SIP_TransactionState.Proceeding)
                            {
                                /* RFC 3261 17.2.1.
                                 *  If a request retransmission is received while in the "Proceeding" state, the most recent provisional
                                 *  response that was received from the TU MUST be passed to the transport layer for retransmission.
                                 */
                                SIP_Response response = this.LastProvisionalResponse;
                                if (response != null)
                                {
                                    this.Stack.TransportLayer.SendResponse(this, response);
                                }
                            }
                            else if (this.State == SIP_TransactionState.Completed)
                            {
                                /* RFC 3261 17.2.1.
                                 *  While in the "Completed" state, if a request retransmission is received, the server SHOULD
                                 *  pass the response to the transport for retransmission.
                                 */
                                this.Stack.TransportLayer.SendResponse(this, this.FinalResponse);
                            }
                        }

                        #endregion

                        #region ACK

                        else if (request.RequestLine.Method == SIP_Methods.ACK)
                        {
                            #region Accepeted

                            if (this.State == SIP_TransactionState.Accpeted)
                            {
                            }

                            #endregion

                            #region Completed

                            else if (this.State == SIP_TransactionState.Completed)
                            {
                                /* 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.
                                 *
                                 *  When this state is entered, timer I is set to fire in T4 seconds for unreliable transports,
                                 *  and zero seconds for reliable transports.
                                 */

                                SetState(SIP_TransactionState.Confirmed);

                                // Stop timers G,H
                                if (m_pTimerG != null)
                                {
                                    m_pTimerG.Dispose();
                                    m_pTimerG = null;

                                    // Log
                                    if (this.Stack.Logger != null)
                                    {
                                        this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer G(INVITE response(3xx - 6xx) retransmission) stopped.");
                                    }
                                }
                                if (m_pTimerH != null)
                                {
                                    m_pTimerH.Dispose();
                                    m_pTimerH = null;

                                    // Log
                                    if (this.Stack.Logger != null)
                                    {
                                        this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer H(INVITE ACK wait) stopped.");
                                    }
                                }

                                // Start timer I.
                                m_pTimerI          = new TimerEx((flow.IsReliable ? 0 : SIP_TimerConstants.T4), false);
                                m_pTimerI.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerI_Elapsed);
                                // Log
                                if (this.Stack.Logger != null)
                                {
                                    this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer I(INVITE ACK retransission wait) started, will trigger after " + m_pTimerI.Interval + ".");
                                }
                                m_pTimerI.Enabled = true;
                            }

                            #endregion
                        }

                        #endregion
                    }

                    #endregion

                    #region Non-INVITE

                    else
                    {
                        // Non-INVITE transaction may have only request retransmission requests.
                        if (this.Method == request.RequestLine.Method)
                        {
                            if (this.State == SIP_TransactionState.Proceeding)
                            {
                                /* RFC 3261 17.2.2.
                                 *  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.
                                 */
                                this.Stack.TransportLayer.SendResponse(this, this.LastProvisionalResponse);
                            }
                            else if (this.State == SIP_TransactionState.Completed)
                            {
                                /* RFC 3261 17.2.2.
                                 *  While 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.
                                 */
                                this.Stack.TransportLayer.SendResponse(this, this.FinalResponse);
                            }
                        }
                    }

                    #endregion
                }
                catch (SIP_TransportException x) {
                    // Log
                    if (this.Stack.Logger != null)
                    {
                        this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] transport exception: " + x.Message);
                    }

                    OnTransportError(x);
                }
            }
        }
 /// <summary>
 /// Default constructor.
 /// </summary>
 /// <param name="request">Incoming SIP request.</param>
 /// <param name="remoteEndpoint">IP end point what made request.</param>
 public SIP_ValidateRequestEventArgs(SIP_Request request, IPEndPoint remoteEndpoint)
 {
     m_pRequest        = request;
     m_pRemoteEndPoint = remoteEndpoint;
 }
Example #19
0
        /// <summary>
        /// Starts terminating dialog.
        /// </summary>
        /// <param name="reason">Termination reason. This value may be null.</param>
        /// <param name="sendBye">If true BYE is sent to remote party.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        public void Terminate(string reason, bool sendBye)
        {
            lock (this.SyncRoot){
                if (this.State == SIP_DialogState.Disposed)
                {
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                if (this.State == SIP_DialogState.Terminating || this.State == SIP_DialogState.Terminated)
                {
                    return;
                }

                m_TerminateReason = reason;

                /* RFC 3261 15.
                 *  The caller's UA MAY send a BYE for either confirmed or early dialogs, and the callee's UA MAY send a BYE on
                 *  confirmed dialogs, but MUST NOT send a BYE on early dialogs.
                 *
                 * RFC 3261 15.1.
                 *  Once the BYE is constructed, the UAC core creates a new non-INVITE client transaction, and passes it the BYE request.
                 *  The UAC MUST consider the session terminated (and therefore stop sending or listening for media) as soon as the BYE
                 *  request is passed to the client transaction. If the response for the BYE is a 481 (Call/Transaction Does Not Exist)
                 *  or a 408 (Request Timeout) or no response at all is received for the BYE (that is, a timeout is returned by the
                 *  client transaction), the UAC MUST consider the session and the dialog terminated.
                 */

                if (sendBye)
                {
                    if ((this.State == SIP_DialogState.Early && m_pActiveInvite is SIP_ClientTransaction) || this.State == SIP_DialogState.Confirmed)
                    {
                        this.SetState(SIP_DialogState.Terminating, true);

                        SIP_Request bye = CreateRequest(SIP_Methods.BYE);
                        if (!string.IsNullOrEmpty(reason))
                        {
                            SIP_t_ReasonValue r = new SIP_t_ReasonValue();
                            r.Protocol = "SIP";
                            r.Text     = reason;
                            bye.Reason.Add(r.ToStringValue());
                        }

                        // Send BYE, just wait BYE to complete, we don't care about response code.
                        SIP_RequestSender sender = CreateRequestSender(bye);
                        sender.Completed += delegate(object s, EventArgs a){
                            this.SetState(SIP_DialogState.Terminated, true);
                        };
                        sender.Start();
                    }
                    else
                    {
                        /* We are "early" UAS dialog, we need todo follwoing:
                         *) If we havent sent final response, send '408 Request terminated' and we are done.
                         *) We have sen't final response, we need to wait ACK to arrive or timeout.
                         *      If will ACK arrives or timeout, send BYE.
                         */

                        if (m_pActiveInvite != null && m_pActiveInvite.FinalResponse == null)
                        {
                            this.Stack.CreateResponse(SIP_ResponseCodes.x408_Request_Timeout, m_pActiveInvite.Request);

                            this.SetState(SIP_DialogState.Terminated, true);
                        }
                        else
                        {
                            // Wait ACK to arrive or timeout.

                            this.SetState(SIP_DialogState.Terminating, true);
                        }
                    }
                }
                else
                {
                    this.SetState(SIP_DialogState.Terminated, true);
                }
            }
        }
Example #20
0
        /// <summary>
        /// Creates new SIP request using this dialog info.
        /// </summary>
        /// <param name="method">SIP method.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>method</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        /// <returns>Returns created request.</returns>
        public SIP_Request CreateRequest(string method)
        {
            if (this.State == SIP_DialogState.Disposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (method == null)
            {
                throw new ArgumentNullException("method");
            }
            if (method == string.Empty)
            {
                throw new ArgumentException("Argument 'method' value must be specified.");
            }

            /* RFC 3261 12.2.1.1.
             *  A request within a dialog is constructed by using many of the
             *  components of the state stored as part of the dialog.
             *
             *  The URI in the To field of the request MUST be set to the remote URI
             *  from the dialog state.  The tag in the To header field of the request
             *  MUST be set to the remote tag of the dialog ID.  The From URI of the
             *  request MUST be set to the local URI from the dialog state.  The tag
             *  in the From header field of the request MUST be set to the local tag
             *  of the dialog ID.  If the value of the remote or local tags is null,
             *  the tag parameter MUST be omitted from the To or From header fields,
             *  respectively.
             *
             *  The Call-ID of the request MUST be set to the Call-ID of the dialog.
             *  Requests within a dialog MUST contain strictly monotonically
             *  increasing and contiguous CSeq sequence numbers (increasing-by-one)
             *  in each direction (excepting ACK and CANCEL of course, whose numbers
             *  equal the requests being acknowledged or cancelled).  Therefore, if
             *  the local sequence number is not empty, the value of the local
             *  sequence number MUST be incremented by one, and this value MUST be
             *  placed into the CSeq header field.  If the local sequence number is
             *  empty, an initial value MUST be chosen using the guidelines of
             *  Section 8.1.1.5.  The method field in the CSeq header field value
             *  MUST match the method of the request.
             *
             *      With a length of 32 bits, a client could generate, within a single
             *      call, one request a second for about 136 years before needing to
             *      wrap around.  The initial value of the sequence number is chosen
             *      so that subsequent requests within the same call will not wrap
             *      around.  A non-zero initial value allows clients to use a time-
             *      based initial sequence number.  A client could, for example,
             *      choose the 31 most significant bits of a 32-bit second clock as an
             *      initial sequence number.
             *
             *  The UAC uses the remote target and route set to build the Request-URI
             *  and Route header field of the request.
             *
             *  If the route set is empty, the UAC MUST place the remote target URI
             *  into the Request-URI.  The UAC MUST NOT add a Route header field to
             *  the request.
             *
             *  If the route set is not empty, and the first URI in the route set
             *  contains the lr parameter (see Section 19.1.1), the UAC MUST place
             *  the remote target URI into the Request-URI and MUST include a Route
             *  header field containing the route set values in order, including all
             *  parameters.
             *
             *  If the route set is not empty, and its first URI does not contain the
             *  lr parameter, the UAC MUST place the first URI from the route set
             *  into the Request-URI, stripping any parameters that are not allowed
             *  in a Request-URI.  The UAC MUST add a Route header field containing
             *  the remainder of the route set values in order, including all
             *  parameters.  The UAC MUST then place the remote target URI into the
             *  Route header field as the last value.
             *
             *  For example, if the remote target is sip:user@remoteua and the route
             *  set contains:
             *      <sip:proxy1>,<sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>
             *
             *  The request will be formed with the following Request-URI and Route
             *  header field:
             *      METHOD sip:proxy1
             *      Route: <sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>,<sip:user@remoteua>
             *
             *  If the first URI of the route set does not contain the lr
             *  parameter, the proxy indicated does not understand the routing
             *  mechanisms described in this document and will act as specified in
             *  RFC 2543, replacing the Request-URI with the first Route header
             *  field value it receives while forwarding the message.  Placing the
             *  Request-URI at the end of the Route header field preserves the
             *  information in that Request-URI across the strict router (it will
             *  be returned to the Request-URI when the request reaches a loose-
             *  router).
             *
             *  A UAC SHOULD include a Contact header field in any target refresh
             *  requests within a dialog, and unless there is a need to change it,
             *  the URI SHOULD be the same as used in previous requests within the
             *  dialog.  If the "secure" flag is true, that URI MUST be a SIPS URI.
             *  As discussed in Section 12.2.2, a Contact header field in a target
             *  refresh request updates the remote target URI.  This allows a UA to
             *  provide a new contact address, should its address change during the
             *  duration of the dialog.
             *
             *  However, requests that are not target refresh requests do not affect
             *  the remote target URI for the dialog.
             *
             *  The rest of the request is formed as described in Section 8.1.1.
             */

            lock (m_pLock){
                SIP_Request request = m_pStack.CreateRequest(method, new SIP_t_NameAddress("", m_pRemoteUri), new SIP_t_NameAddress("", m_pLocalUri));
                request.Route.RemoveAll();
                if (m_pRouteSet.Length == 0)
                {
                    request.RequestLine.Uri = m_pRemoteTarget;
                }
                else
                {
                    SIP_Uri topmostRoute = ((SIP_Uri)m_pRouteSet[0].Address.Uri);
                    if (topmostRoute.Param_Lr)
                    {
                        request.RequestLine.Uri = m_pRemoteTarget;
                        for (int i = 0; i < m_pRouteSet.Length; i++)
                        {
                            request.Route.Add(m_pRouteSet[i].ToStringValue());
                        }
                    }
                    else
                    {
                        request.RequestLine.Uri = SIP_Utils.UriToRequestUri(topmostRoute);
                        for (int i = 1; i < m_pRouteSet.Length; i++)
                        {
                            request.Route.Add(m_pRouteSet[i].ToStringValue());
                        }
                    }
                }
                request.To.Tag   = m_RemoteTag;
                request.From.Tag = m_LocalTag;
                request.CallID   = m_CallID;
                // ACK won't increase sequence.
                if (method != SIP_Methods.ACK)
                {
                    request.CSeq.SequenceNumber = ++m_LocalSeqNo;
                }
                request.Contact.Add(m_pLocalContact.ToString());

                return(request);
            }
        }
        /// <summary>
        /// Creates authorization for each challange in <b>response</b>.
        /// </summary>
        /// <param name="request">SIP request where to add authorization values.</param>
        /// <param name="response">SIP response which challanges to authorize.</param>
        /// <param name="credentials">Credentials for authorization.</param>
        /// <returns>Returns true if all challanges were authorized. If any of the challanges was not authorized, returns false.</returns>
        private bool Authorize(SIP_Request request, SIP_Response response, NetworkCredential[] credentials)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            if (response == null)
            {
                throw new ArgumentNullException("response");
            }
            if (credentials == null)
            {
                throw new ArgumentNullException("credentials");
            }

            bool allAuthorized = true;

            #region WWWAuthenticate

            foreach (SIP_t_Challenge challange in response.WWWAuthenticate.GetAllValues())
            {
                Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData, request.RequestLine.Method);

                // Serach credential for the specified challange.
                NetworkCredential credential = null;
                foreach (NetworkCredential c in credentials)
                {
                    if (c.Domain.ToLower() == authDigest.Realm.ToLower())
                    {
                        credential = c;
                        break;
                    }
                }
                // We don't have credential for this challange.
                if (credential == null)
                {
                    allAuthorized = false;
                }
                // Authorize challange.
                else
                {
                    authDigest.UserName = credential.UserName;
                    authDigest.Password = credential.Password;
                    authDigest.CNonce   = Auth_HttpDigest.CreateNonce();
                    authDigest.Uri      = request.RequestLine.Uri.ToString();

                    request.Authorization.Add(authDigest.ToAuthorization());
                }
            }

            #endregion

            #region ProxyAuthenticate

            foreach (SIP_t_Challenge challange in response.ProxyAuthenticate.GetAllValues())
            {
                Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData, request.RequestLine.Method);

                // Serach credential for the specified challange.
                NetworkCredential credential = null;
                foreach (NetworkCredential c in credentials)
                {
                    if (c.Domain.ToLower() == authDigest.Realm.ToLower())
                    {
                        credential = c;
                        break;
                    }
                }
                // We don't have credential for this challange.
                if (credential == null)
                {
                    allAuthorized = false;
                }
                // Authorize challange.
                else
                {
                    authDigest.UserName = credential.UserName;
                    authDigest.Password = credential.Password;
                    authDigest.CNonce   = Auth_HttpDigest.CreateNonce();
                    authDigest.Uri      = request.RequestLine.Uri.ToString();

                    request.ProxyAuthorization.Add(authDigest.ToAuthorization());
                }
            }

            #endregion

            return(allAuthorized);
        }
Example #22
0
 /// <summary>
 /// SIP_Request.CreateResponse constructor.
 /// </summary>
 /// <param name="request">Owner request.</param>
 internal SIP_Response(SIP_Request request)
 {
     m_pRequest = request;
 }
 /// <summary>
 /// Default constructor.
 /// </summary>
 /// <param name="stack">Reference to SIP stack.</param>
 /// <param name="flow">SIP data flow.</param>
 /// <param name="request">Recieved request.</param>
 internal SIP_RequestReceivedEventArgs(SIP_Stack stack, SIP_Flow flow, SIP_Request request) : this(stack, flow, request, null)
 {
 }
        /// <summary>
        /// Is called when client transactions receives response.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void ClientTransaction_ResponseReceived(object sender, SIP_ResponseReceivedEventArgs e)
        {
            lock (m_pLock){
                m_pFlow = e.ClientTransaction.Request.Flow;

                if (e.Response.StatusCode == 401 || e.Response.StatusCode == 407)
                {
                    // Check if authentication failed(We sent authorization data and it's challenged again,
                    // probably user name or password inccorect)
                    bool hasFailedAuthorization = false;
                    foreach (SIP_t_Challenge challange in e.Response.WWWAuthenticate.GetAllValues())
                    {
                        foreach (SIP_t_Credentials credentials in m_pTransaction.Request.Authorization.GetAllValues())
                        {
                            if (new Auth_HttpDigest(challange.AuthData, "").Realm == new Auth_HttpDigest(credentials.AuthData, "").Realm)
                            {
                                hasFailedAuthorization = true;
                                break;
                            }
                        }
                    }
                    foreach (SIP_t_Challenge challange in e.Response.ProxyAuthenticate.GetAllValues())
                    {
                        foreach (SIP_t_Credentials credentials in m_pTransaction.Request.ProxyAuthorization.GetAllValues())
                        {
                            if (new Auth_HttpDigest(challange.AuthData, "").Realm == new Auth_HttpDigest(credentials.AuthData, "").Realm)
                            {
                                hasFailedAuthorization = true;
                                break;
                            }
                        }
                    }

                    // Authorization failed, pass response to UA.
                    if (hasFailedAuthorization)
                    {
                        OnResponseReceived(e.Response);
                    }
                    // Try to authorize challanges.
                    else
                    {
                        SIP_Request request = m_pRequest.Copy();

                        /* RFC 3261 22.2.
                         *  When a UAC resubmits a request with its credentials after receiving a
                         *  401 (Unauthorized) or 407 (Proxy Authentication Required) response,
                         *  it MUST increment the CSeq header field value as it would normally
                         *  when sending an updated request.
                         */
                        request.CSeq = new SIP_t_CSeq(m_pStack.ConsumeCSeq(), request.CSeq.RequestMethod);

                        // All challanges authorized, resend request.
                        if (Authorize(request, e.Response, this.Credentials.ToArray()))
                        {
                            SIP_Flow flow = m_pTransaction.Flow;
                            CleanUpActiveTransaction();
                            SendToFlow(flow, request);
                        }
                        // We don't have credentials for one or more challenges.
                        else
                        {
                            OnResponseReceived(e.Response);
                        }
                    }
                }
                else
                {
                    OnResponseReceived(e.Response);
                    if (e.Response.StatusCodeType != SIP_StatusCodeType.Provisional)
                    {
                        OnCompleted();
                    }
                }
            }
        }