SIP Example: Usage of the SIP Stack

The SIP sample application is a game that can be played between two users that use two different machines. The following figure illustrates the sample application.

All users who want to play the game must register to the server with their identities. This informs the server that the user is active and prepared to participate in the game. If User 1 wants to play the game with User 2 then it must send a request to User 2 through the server. When User 2 accepts the request and acknowledges it then the two users can start playing. A user can disconnect from playing the game by sending a message. If a user wants to be deactivated then it can deregister from the server.

This application is developed using the key features of the Symbian SIP stack. It shows how to create profiles and do SIP registration using the profiles, how to set up a SIP session and send an instant message using the SIP. For example you can set up the SIP server using Brekeke and you can download the same from http://www.brekeke.com/download/download_sip_2_0.php.

Features of the game

The main features of the game are:

  • Register or deregister - A user registers to the server with a unique identity before playing the game. This identity is the SIP URI of the user. The user registers by sending a SIP REGISTER message to the SIP server and is active. A user deactivates by deregistering to the server. This activity is done by sending the SIP REGISTER message with the expires header set to 0. For more information on registering or deregistering to the server see, Registering or deregistering an user.

  • Invite another user - A user invites another user to play the game by entering the SIP URI of the other user in the INVITE message. For more information on inviting another user to play the game see, Inviting a participant for session setup.

  • Send a message to another user - A user can send an instant message to another user. For more information on how to send an instant message to another user see, Sending an instant message.

  • Disconnect from playing the game - A user can disconnect from playing the game by sending a SIP BYE message to the other user.

How to play the game

The following snapshot shows the different tabs available in the game.

The following list describes the different tabs available in the game:

  • Invite for game - This tab allows you to invite a user to play the game. When you invoke this tab, a UI screen appears which asks the user to provide the SIP URI of the second user with whom the user wants to play the game. The SIP URI of the second user is then sent to the SIP stack which passes on the invite request to the second user. The second user can accept or reject the invite. If the second user accepts the invite then it acknowledges by sending a message that it is prepared to start playing the game. If the second user rejects the invite then it cannot play the game.

  • Enable profile - This tab allows you to enable a profile. When you invoke this tab, a UI screen appears where the user enters the values. A profile is created using these values and the user registers with the SIP server.

  • Send instant msg - This tab allows you to send a message to another user. When you invoke this tab, a message with the address of the recipient is sent to the SIP stack. The SIP stack sends this message to the recipient address.

  • End game - This tab allows you to disconnect from playing the game. When you invoke this tab, a BYE message is sent to the other user and the game is terminated.

Registering or deregistering an user

An application can register to the SIP Server using:

  • the CSIPRegistrationBinding class and its methods

  • the SIP Profile Agent

This sample application uses the SIP Profile Agent method to do SIP registration. To use this method you need a SIP profile setting, and you must create a SIP profile before you start using the application. Note: A list of SIP profile settings can be found in the CSIPProfile class.

The SIP profile agent method provides APIs for:

  • Registering and Deregistering

  • Listening to registration events

  • Creating, modifying, and deleting the SIP profile settings that the profile agent plug-in implementations use for SIP registration.

The following code from SIPExProfileQueryDlg.cpp shows how the sample application stores the settings entered by users on the SIP profile store.

//Create a new profile
    CSIPManagedProfile* profile = iMProfileRegistry->CreateL(iProfileData.iServiceProfile);
   
//Copy attributes entered by the user to the new profile        
    iNewProfile = profile;
    profile = CopyDataToProfileL();

//Set the profile to default and save some settings
    TInt err= profile->SetParameter(KSIPDefaultProfile,ETrue);
   
//Save the profile to persistent store                
    iMProfileRegistry->SaveL( *profile );

The applications can start SIP registration or deregistration using the Enable or Disable methods from the SIP profile API.

The following code from SIPExSIPEngine.cpp shows how to register using the profile agent.

//Check for the existing profile
    if ( iProfile )
        {
        delete iProfile;
        iProfile = NULL;
        }
    TBool registered( EFalse );
    
//Leaves with KErrNotFound if default profile is not found
    iProfile = iProfileRegistry->DefaultProfileL();
 else
        {
        const TDesC8* aor = NULL;
  iProfile->GetParameter( KSIPUserAor, aor );      
  iProfileRegistry->EnableL( *iProfile, *this );

    //Check if the profile was immediately set to the registered state
    iProfile->GetParameter( KSIPProfileRegistered, registered );
        }

The following code from SIPExSIPEngine.cpp shows how to deregister using the profile agent.

if ( iProfile )
        {
        iProfileRegistry->Disable( *iProfile );
        delete iProfile;
        iProfile = NULL;        
        }

The SIP profile API also provides a notification method that is used by an application to monitor profile related events.

The following code from SIPExProfileQueryDlg.cpp shows the profile related events that the sample applications monitors.

void ProfileRegistryEventOccurred( 
    TUint32 aProfileId, 
    TEvent aEvent )
    {
    switch (aEvent)
        {
        case MSIPProfileRegistryObserver::EProfileRegistered:
            {
            HandleProfileRegistered( aProfileId );
            break;
            }
        case MSIPProfileRegistryObserver::EProfileDeregistered:
            {
            HandleProfileDeregistered( aProfileId );
            break;
            }
        case MSIPProfileRegistryObserver::EProfileDestroyed:
            {
            HandleProfileDestroyed( aProfileId );
            break;
            }
        default:
            {
            // MSIPProfileRegistryObserver::EProfileCreated and MSIPProfileRegistryObserver::EProfileUpdated are ignored
            break;
            }
        }
    }
void ProfileRegistryErrorOccurred( 
    TUint32 /* aSIPProfileId */,
    TInt aError )
    {
    iObserver->ProfileError( aError );
    }

Classes containing APIs in scope

CSIPProfile, CSIPManagedProfile, CSIPProfileRegistry, CSIPManagedProfileRegistry, MSIPProfileRegistryObserver.

Inviting a participant for session setup

The sample application uses the SIP INVITE method to setup a session between two users. In this case, the application does not accept any SDP parameters from the user and instead sends fixed SDP values.

The following code from SIPExSIPIdleState.cpp shows how the application sends an INVITE message.

void SendInviteL( 
    CSIPExSIPEngine& aEngine,
    const TDesC8& aSipUri )
    {
    //Retrieve the active profile and connection
    CSIPProfile& prof = aEngine.Profile();
    CSIPConnection& conn = aEngine.ConnectionL();

 //Create CUri8 from the passed descriptor. This value is given by the user
    CUri8* uri8 = aEngine.ConvertToUri8LC( aSipUri );
    
    //Get dialog association, save for future use
    //The ownership of uri8 is transferred
    CSIPInviteDialogAssoc* dialogAssoc =
        CSIPInviteDialogAssoc::NewL( conn, uri8, prof );
        aEngine.SetDialogAssoc( *dialogAssoc ); //Ownership is transferred!!

    //Create the necessary message elements
    CSIPMessageElements* msgElements = aEngine.CreateMessageElementsLC();

    //Send the INVITE in the dialog
    //The ownership of msgElements is transferred
    INVITE SENT OUT
    CSIPClientTransaction* tx = dialogAssoc->SendInviteL( msgElements );
 }

Handling an incoming invite

An application that wants to receive an incoming invite outside the dialog must implement the Client Resolver API. The CSipResolvedClient (SipResolvedClient.h) interface is implemented by clients to enable a client resolution mechanism when the SIP requests are received outside the SIP dialog. The application must state the capabilities, that is the supported content types and media formats. This is done using the SIP headers and SDP m-lines either in the code of the plug-in, that is in const KCapabilities or in the opaque_data field of the resource file.

The capabilities can be provided to the plug-ins in two ways:

  • The data is provided in the ECOM resource file.

  • In the interface implementation from where it is used to determine the target client.

The following code from SIPExResolverPlugin.cpp shows how the data is provided in the ECOM resource file.

#include <RegistryInfo.rh>
RESOURCE REGISTRY_INFO theInfo
    {
    // UID for the DLL
    dll_uid = 0xA00001EC;
    // Declare array of interface info
    interfaces = 
        {
        INTERFACE_INFO 
            {
            // UID of interface that is implemented
            interface_uid = 0x102010DD;
            implementations = 
                {
                IMPLEMENTATION_INFO
                    {
                    implementation_uid = 0xA00001EC;
                    version_no = 1;
                    // SIPEx UID: Must match to the one SIPEx passes to CSIP::NewL.    
                    default_data = "A00001EB";
                    }
                };
            }
        };
    }

The following code from SIPExResolverPlugin.cpp shows how the capabilities are defined in the plug-in.

//Code for defining capabilities in plugin
//File : SIPExResolverPlugin.cpp

_LIT8(KCapabilities,
"<SIP_CLIENT ALLOW_STARTING=\"YES\">\
<SIP_HEADERS>\
<ACCEPT value=\"application/sdp\"/>\
<ACCEPT value=\"SIPEx/InstantMessage\"/>\
</SIP_HEADERS>\
<SDP_LINES>\
<LINE name=\"m\" value=\"application 0 TCP SIPEx\"/>\
</SDP_LINES>\
</SIP_CLIENT>");


When there is an incoming Invite Request with SDP parameters the SIP stack passes the request to the Client Resolver to get the resolved client for that request. The Client Resolver gets the capabilities for all the applications that implement the Client Resolver API.

The following code from SIPExResolverPlugin.cpp shows how to get the capabilities for an application.

//Code for obtaining capabilities for an application
//File : SIPExResolverPlugin.cpp

const TDesC8& CSIPExResolverPlugin::Capabilities()
    {
    return KCapabilities;
    }

When the target client is identified, the Client Resolver gets the ChannelID from the plug-in implementation and requests the resolved client to connect to the SIP implementation on that UID. Note: The ChannelID is same as Application UID3

The following code from SIPExResolverPlugin.cpp shows how to get ChannelID for an application.

//Code for obtaining ChannelID for an application
//File : SIPExResolverPlugin.cpp
  
  TUid CSIPExResolverPlugin::ChannelL( RStringF /*aMethod*/,
    const TDesC8& /*aRequestUri*/,
    const RPointerArray<CSIPHeaderBase>& /*aHeaders*/,
    const TDesC8& /*aContent*/,
    const CSIPContentTypeHeader* /*aContentType*/)
    {
    return iApplicationUID;
    }

//Code for asking an application to connect on UID 
//File : SIPExResolverPlugin.cpp

 void CSIPExResolverPlugin::ConnectL( TUid aUid )
    {
    //Launch application is based on UID passed from the SIP stack
    
    TApaAppInfo appInfo;
    User::LeaveIfError( iApaSession.GetAppInfo( appInfo, aUid ) );
    CApaCommandLine* cmdLine = CApaCommandLine::NewLC();
#ifdef EKA2
    cmdLine->SetExecutableNameL( appInfo.iFullName );
#else
    cmdLine->SetLibraryNameL( appInfo.iFullName );
#endif
    User::LeaveIfError( iApaSession.StartApp( *cmdLine ) );
    CleanupStack::PopAndDestroy( cmdLine ); 
    }

When the resolved client connects, the INVITE is forwarded to the client and it starts ringing (180 Ringing) and sends the provisional response to the calling party. This part is implemented in InviteReceivedL as shown in the following code.

The following code from SIPExSIPIdleState.cpp shows how the application gets the INVITE and how the provisional response is sent.

/Code for CSIPExSIPIdleState::InviteReceivedL()... 
//File : SIPExSIPIdleState.cpp    

void CSIPExSIPIdleState::InviteReceivedL( 
    CSIPExSIPEngine& aEngine,
    CSIPServerTransaction* aTransaction )
    {
    _LIT8( KLogEntry, "180 Ringing sent" );
    ..
    ..
    aEngine.Observer()->WriteLog( KLogEntry );
    ..
   }

From this stage, the INVITE is sent to InviteReceived of CSIPExEngine in SIPExGameEngine.cpp from where it asks from the game observer if the user has accepted the invitation or not. The game starts or does not start depends on action taken.

The following code from SIPExStateRegistered.cpp shows how the acceptance is asked from the game observer.

//The acceptance is asked from the user and if accepted the game is reset, start
//listening socket and signal the SIP engine to send Accepted to the remote peer.

//File : SIPExStateRegistered.cpp

void TSIPExStateRegistered::InviteReceived( 
    CSIPExEngine* aContext, 
    const TDesC8& aFrom, 
    const TUint32 aIapId )
    {
    iEnded = EFalse;
    
    TBool retVal( EFalse );
    TRAPD( ignore, retVal = aContext->AcceptInvitationL( aFrom ) );
    if ( iEnded )
        {
        return;
        }
    if( retVal )
        {
        StatusInfo( aContext, KGameStarting() );
        ChangeState( aContext, aContext->iStateAcceptingSIP );
        aContext->ResetGame();
        aContext->SetPeer( CSIPExEngine::EServer );
        Info( aContext, KListening() );
        TInetAddr addr;
        TRAP( ignore, addr = 
                        aContext->SocketEngineL()->StartListeningL( aIapId ) );
        Info( aContext, KAccepting() );
        TRAP( ignore, aContext->SIPEngine()->AcceptInviteL( addr ) );
        Info( aContext, KWaitingRemoteConn() );
        }
    else 
        {
        TRAP( ignore, aContext->SIPEngine()->DeclineInviteL() );
        Info( aContext, KInviteDeclined() );
        }
    }

When the game observer accepts the invitation the following code from SIPExGameEngine.cpp is run.

//File : SIPExGameEngine.cpp

TBool CSIPExEngine::AcceptInvitationL( const TDesC8& aFrom )
    {
    HBufC* from = HBufC::NewLC( aFrom.Length() );
    from->Des().Copy( aFrom );
    TBool retVal = iGameObserver.AcceptInvitationL( *from );
    CleanupStack::PopAndDestroy( from );
    return retVal;
    }

Sending an instant message

This sample application allows a user to send an Instant Message (IM) to another user. The user requires SIP URI of the other user and the message content that the user must enter. The application then creates a MESSAGE (request message) with these parameters and uses SendRequestL() to send it.

The following code from SIPExSIPEngine.cpp shows how to send an instant message.

//Create and send an instant message to a recipient defined with the parameters.
//This is implemented with the MESSAGE method and is sent outside of a dialog.

CreateIML(const TDesC8& aMessage,
    const TDesC8& aSipUri )
    {
    _LIT8( KMediaType, "SIPEx" );    // Part of content type
    _LIT8( KMediaSubType, "InstantMessage" );    // Part of content type

//Create the necessary elements of the IM
    
CSIPRequestElements* reqElem = CreateReqElementsLC( aSipUri );
    CSIPToHeader* toHeader = CreateToHeaderLC( aSipUri );
    reqElem->SetToHeaderL( toHeader );
    
//Create the fromHeader value using the information from the profile

 const TDesC8* aor = NULL;
 iProfile->GetParameter( KSIPUserAor, aor ); 
 CSIPAddress* addr = CSIPAddress::DecodeL( *aor );
 CSIPFromHeader* fromHeader = CSIPFromHeader::NewL( addr );
 reqElem->SetFromHeaderL( fromHeader );
 reqElem->SetMethodL( SIPStrings::StringF( SipStrConsts::EMessage ) );

//Get reference to the message elements from the request elements, create and insert content type header (ownership of the content type object is transferred)

    CSIPMessageElements& msgElem = reqElem->MessageElements();
    CSIPContentTypeHeader* ct =    CSIPContentTypeHeader::NewLC( KMediaType, KMediaSubType );
    msgElem.SetContentL( aMessage.AllocL(), ct );
    
//Get the current connection

CSIPConnection& conn = ConnectionL();

//Send the request using the connection (ownership of the request elements object is transferred)

    CSIPClientTransaction* ctx = conn.SendRequestL( reqElem );
    delete ctx;
    }

Classes containing APIs in scope

CSIPRequestElements, CSIPToHeader, CSIPFromHeader, CSIPAddress, CSIPMessageElements, CSIPClientTransaction.