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.
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.
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.
An application can register to the SIP Server using:
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
.
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 ); }
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; }
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
.