Click here to Skip to main content
15,891,431 members
Articles / Programming Languages / C#

SIP Stack with SIP Proxy - (VOIP)

Rate me:
Please Sign up or sign in to vote.
4.86/5 (45 votes)
11 Jun 2007CPOL2 min read 1.6M   28.1K   162  
C# implementation of SIP
using System;
using System.Collections.Generic;
using System.Text;
using System.Timers;

using LumiSoft.Net.SIP.Message;
using LumiSoft.Net.SIP.Stack;

namespace LumiSoft.Net.SIP.Proxy
{
    #region Delegates

    /// <summary>
    /// Represents the method that will handle the SIP_Registrar.CanRegister event.
    /// </summary>
    /// <param name="userName">Authenticated user name.</param>
    /// <param name="address">Address to be registered.</param>
    /// <returns>Returns true if specified user can register specified address, otherwise false.</returns>
    public delegate bool SIP_CanRegisterEventHandler(string userName,string address);

    #endregion

    /// <summary>
    /// This class implements SIP registrar server. Defined in RFC 3261.
    /// </summary>
    public class SIP_Registrar
    {
        private SIP_ProxyCore              m_pProxy         = null;
        private SIP_Stack                  m_pSipStack      = null;
        private SIP_RegistrationCollection m_pRegistrations = null;
        private Timer                      m_pTimer         = null;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="proxy">Owner proxy.</param>
        internal SIP_Registrar(SIP_ProxyCore proxy)
        {
            m_pProxy    = proxy;
            m_pSipStack = m_pProxy.Stack;

            m_pRegistrations = new SIP_RegistrationCollection();

            m_pTimer = new Timer(15000);
            m_pTimer.Elapsed += new ElapsedEventHandler(m_pTimer_Elapsed);
            m_pTimer.Enabled = true;
        }


        #region method m_pTimer_Elapsed

        private void m_pTimer_Elapsed(object sender,ElapsedEventArgs e)
        {
            m_pRegistrations.RemoveExpired();
        }

        #endregion


        #region method GetRegistration

        /// <summary>
        /// Gets specified registration. Returns null if no such registration.
        /// </summary>
        /// <param name="addressOfRecord">Address of record of registration which to get.</param>
        /// <returns>Returns SIP registration or null if no match.</returns>
        public SIP_Registration GetRegistration(string addressOfRecord)
        {
            return m_pRegistrations[addressOfRecord];
        }

        #endregion

        #region method SetRegistration

        /// <summary>
        /// Add or updates specified SIP registration info.
        /// </summary>
        /// <param name="addressOfRecord">Registration address of record.</param>
        /// <param name="contacts">Registration address of record contacts to update.</param>
        public void SetRegistration(string addressOfRecord,SIP_t_ContactParam[] contacts)
        {
            SetRegistration(addressOfRecord,contacts,null);
        }

        /// <summary>
        /// Add or updates specified SIP registration info.
        /// </summary>
        /// <param name="addressOfRecord">Registration address of record.</param>
        /// <param name="contacts">Registration address of record contacts to update.</param>
        /// <param name="localEP">SIP proxy local end point info what accpeted this contact registration.</param>
        public void SetRegistration(string addressOfRecord,SIP_t_ContactParam[] contacts,SIP_EndPointInfo localEP)
        {
            lock(m_pRegistrations){
                SIP_Registration registration = m_pRegistrations[addressOfRecord];
                if(registration == null){
                    registration = new SIP_Registration("system",addressOfRecord);
                    m_pRegistrations.Add(registration);
                }

                registration.UpdateContacts(contacts,180,m_pSipStack.MinimumExpireTime,localEP);
            }
        }

        #endregion

        #region method DeleteRegistration

        /// <summary>
        /// Deletes specified registration and all it's contacts.
        /// </summary>
        /// <param name="addressOfRecord">Registration address of record what to remove.</param>
        public void DeleteRegistration(string addressOfRecord)
        {
            m_pRegistrations.Remove(addressOfRecord);
        }

        #endregion

        #region method GetContact

        /// <summary>
        /// Gets registration contact.
        /// </summary>
        /// <param name="uri">SIP URI to get.</param>
        /// <param name="retVal">If specified SIP URI is registration contact, then that contect will be stored here.</param>
        /// <returns>Returns true is specified SIP URI is registration contact.</returns>
        internal bool GetContact(SIP_Uri uri,out SIP_RegistrationContact retVal)
        {
            retVal = null;

            lock(m_pRegistrations){
                foreach(SIP_Registration registration in m_pRegistrations){
                    foreach(SIP_RegistrationContact c in registration.Contacts){
                        if(c.Contact.Address.IsSipUri && SIP_Uri.Parse(c.Contact.Address.Uri).Equals(uri)){
                            retVal = c;
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        #endregion


        #region method Register

        /// <summary>
        /// Handles REGISTER method.
        /// </summary>
        /// <param name="e">Request event arguments.</param>
        internal void Register(SIP_RequestReceivedEventArgs e)
        {
            /* RFC 3261 10.3 Processing REGISTER Requests.
                1. The registrar inspects the Request-URI to determine whether it
                    has access to bindings for the domain identified in the Request-URI.
                      
                2. To guarantee that the registrar supports any necessary extensions, 
                   the registrar MUST process the Require header field.
                     
                3. A registrar SHOULD authenticate the UAC.
                     
                4. The registrar SHOULD determine if the authenticated user is
                   authorized to modify registrations for this address-of-record.
                     
                5. The registrar extracts the address-of-record from the To header
                   field of the request.  If the address-of-record is not valid
                   for the domain in the Request-URI, the registrar MUST send a
                   404 (Not Found) response and skip the remaining steps.
                     
                6. The registrar checks whether the request contains the Contact
                   header field.  If not, it skips to the last step.  If the
                   Contact header field is present, the registrar checks if there
                   is one Contact field value that contains the special value "*"
                   and an Expires field.  If the request has additional Contact
                   fields or an expiration time other than zero, the request is
                   invalid, and the server MUST return a 400 (Invalid Request).
                     
                7. The registrar now processes each contact address in the Contact
                   eader field in turn.
                     
                   If Expire paremeter specified, check that it isnt smaller than server minimum
                   allowed expire value. If smaller return error 423 (Interval Too Brief) and add 
                   header field Min-Expires. If no expire parameter, user server default value.                          
                     
                8. The registrar returns a 200 (OK) response.  The response MUST contain Contact 
                   header field values enumerating all current bindings. Each Contact value MUST 
                   feature an "expires" parameter indicating its expiration interval chosen by the
                   registrar. The response SHOULD include a Date header field.

            */

            SIP_Request request = e.Request;

            // 3. Authenticate request
            string userName = null;
            if(!m_pProxy.AuthenticateRequest(e,out userName)){
                return;
            }
                        
            // 5. Get To:.
            // We have not SIP URI, To: may be SIP URI only.
            if(!(request.To.Address.IsSipUri || request.To.Address.IsSecureSipUri)){
                e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x400_Bad_Request + ": To: value must be SIP URI."));
                return;
            }
            SIP_Uri to = SIP_Uri.Parse(request.To.Address.Uri);

            // 4. Check if user can register specified address.
            // User is not allowed to register specified address.
            if(!OnCanRegister(userName,to.Address)){
                e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x403_Forbidden));
                return;
            }
                                        
            //--- 6. And 7. --------------------------------------------------------
            SIP_t_ContactParam[] contacts = request.Contact.GetAllValues();
            if(request.Header.Contains("Contact:")){                
                SIP_t_ContactParam starContact = null;
                foreach(SIP_t_ContactParam contact in contacts){
                    // We have STAR contact, store it.
                    if(starContact == null && contact.IsStarContact){
                        starContact = contact;                               
                    }

                    /* RFC 3261 10.2.2.
                        Expire value 0 in Expire header or contact expire parameter, means that specified
                        contact must be removed. 
                        If STAR contact then expires must be always 0.
                    */

                    //--- Handle minimum expires time --------------------------------------------
                    // Get contact expires time, if not specified, get header expires time, if not specified,
                    // use server minimum value.
                    int expires = contact.Expires;
                    if(expires == -1){
                        expires = request.Expires;
                    }
                    if(expires == -1){
                        expires = m_pSipStack.MinimumExpireTime;
                    }
                    // We don't check that for STAR contact and if contact expires parameter = 0.
                    if(!contact.IsStarContact && expires != 0 && expires < m_pSipStack.MinimumExpireTime){                        
                        SIP_Response sipExpiresResponse = request.CreateResponse(SIP_ResponseCodes.x423_Interval_Too_Brief);

                        // RFC 3261 20.23 must add Min-Expires for "Interval Too Brief".
                        sipExpiresResponse.MinExpires = m_pSipStack.MinimumExpireTime;

                        // The response SHOULD include a Date header field.
                        sipExpiresResponse.Date = DateTime.Now;
                                               
                        // Send response
                        e.ServerTransaction.SendResponse(sipExpiresResponse);
                        return;
                    }
                    //---------------------------------------------------------------------------
                }

                // We have STAR contact. Check that STAR contact meets all RFC rules.
                if(starContact != null){
                    // RFC 3261 10.3-6. We may have only 1 STAR Contact and Expires: header field must be 0.
                    if(contacts.Length > 1 || request.Expires != 0){                        
                        e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x400_Bad_Request + ". Invalid STAR Contact: combination or parameter. For more info see RFC 3261 10.3.6."));
                        return;
                    }
                    // We have valid STAR Contact:.
                    //else{
                    //}
                }
            }
                    
            // Add or update SIP registration
            SIP_Registration registration = null;
            lock(m_pRegistrations){
                if(!m_pRegistrations.Contains(to.Address)){
                    // Add SIP registration.
                    registration = new SIP_Registration(userName,to.Address);
                    m_pRegistrations.Add(registration);
                }
                // Update SIP registration contacts
                else{
                    registration = m_pRegistrations[to.Address];
                }
            }
                        
            // Update registration contacts.
            registration.UpdateContacts(contacts,request.Expires,m_pSipStack.MinimumExpireTime,SIP_Utils.ToEndPointInfo(e.Request.Socket,true));
            //--------------------------------------------------------------------

            // 8. --- Make and send SIP respone ----------------------------------
            SIP_Response sipResponse = request.CreateResponse(SIP_ResponseCodes.x200_Ok);
            
            // The response SHOULD include a Date header field.
            sipResponse.Date = DateTime.Now;

            // List Registered Contacts. We also need to return expires parameter with remaining time.
            sipResponse.Header.RemoveAll("Contact:");
            foreach(SIP_RegistrationContact contact in registration.Contacts){
                // Don't list expired contacts what wait to be disposed.
                if(contact.Expires > 1){
                    sipResponse.Header.Add("Contact:",contact.ToStringValue());
                }
            }

            // Add Authentication-Info:, then client knows next nonce.
            sipResponse.AuthenticationInfo.Add("qop=\"auth\",nextnonce=\"" + m_pSipStack.DigestNonceManager.CreateNonce() + "\"");
                                               
            // Send response
            e.ServerTransaction.SendResponse(sipResponse);
            //-----------------------------------------------------------------------
        }

        #endregion


        #region Properties Implementation

        /// <summary>
        /// Gets owner proxy core.
        /// </summary>
        public SIP_ProxyCore Proxy
        {
            get{ return m_pProxy; }
        }

        /// <summary>
        /// Gets current SIP registrations.
        /// </summary>
        public SIP_Registration[] Registrations
        {
            get{
                lock(m_pRegistrations){
                    SIP_Registration[] retVal = new SIP_Registration[m_pRegistrations.Count];
                    m_pRegistrations.Values.CopyTo(retVal,0);

                    return retVal;
                }
            }
        }

        #endregion

        #region Events Implementation

        /// <summary>
        /// This event is raised when SIP registrar need to check if specified user can register specified address.
        /// </summary>
        public event SIP_CanRegisterEventHandler CanRegister = null;

        #region method OnCanRegister

        /// <summary>
        /// Is called by SIP registrar if it needs to check if specified user can register specified address.
        /// </summary>
        /// <param name="userName">Authenticated user name.</param>
        /// <param name="address">Address to be registered.</param>
        /// <returns>Returns true if specified user can register specified address, otherwise false.</returns>
        internal bool OnCanRegister(string userName,string address)
        {
            if(this.CanRegister != null){
                return this.CanRegister(userName,address);
            }

            return false;
        }

        #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
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions