Sockets

Sockets provide higher-level access to communications protocols than seen in the Serial Communication section. The Sockets API was originally designed to facilitate TCP/IP connections in BSD UNIX, and it has become a standard API for doing so across a range of platforms, including UNIX, Windows and now Series 60. If you have used sockets before on other platforms, then many of the concepts explained here will be quite familiar already.

In addition to TCP/IP connections, the Sockets API is sufficiently generic to allow for other underlying network types and protocols. This fact has been exploited in Series 60, where the Sockets API can be used to make connections over protocols such as infrared, Bluetooth and WAP.

This section outlines the key essentials of sockets programming and provides an example application discussed in a step-by-step fashion. The Series 60 APIs for secure sockets programming are also discussed.

The subsections that follow present the basics you will need to understand in order to use sockets on Series 60.

Sockets on Series 60

The structure of the Series 60 Sockets API is very similar to that already seen for serial communication. The key components are:

  • A Socket Server, to provide shared, generic access to sockets protocols.

  • A client-side API, providing controlled access to this server.

  • A system of server plug-ins (here known as protocol modules , or PRTs , analogous to the CSYs used by the Serial Comms Server).

The internal name of the Symbian OS Socket Server is " ESOCK ", so you may see this used in the SDK documentation and elsewhere to refer to this server.

The plug-in protocol modules are just DLLs with a .prt file extension. You do not use these directlythey are used only by the Socket Server itself. Each protocol module specifies its capabilities in an .esk text file, which can be found in the \system\data folder on a device or in the equivalent PC emulator directory. The .esk files replace an earlier architecture which used a single configuration file, named esock.ini . Again, you may see this file referred to in other documentationyou should understand that it no longer exists and has been replaced by a set of .esk files, one per protocol.

Client and Server

A socket represents one end of a connection between two participants . In any sockets scenario, one participant is referred to as the server and the other as the client . This distinction is really meaningful only at connection timea server passively waits for incoming connections, whereas the client actively requests a connection with a server. Once connected, both client and server may write data to and/or read data from the other participant.

A single program can be written to perform either role. As you will see, the IrSockets example application can behave as either client or server, depending on a menu command chosen by the user .

It is important not to confuse the terms client and server described here with the Symbian OS Client/Server framework. A server can indeed be implemented in a Symbian OS client process and, for example, connect to the Symbian OS Socket Server.


Connectionless and Connected Sockets

A socket is said to be either connectionless or connected (the latter is also referred to as connection-oriented ). Connectionless sockets simply ensure delivery of datagrams (data packets) from one participant to another. There is no concept of an ongoing "session" between the two participants, and this entails a number of important consequences:

  • Each time you write data to a connectionless socket, you must specify the target participant's address.

  • Likewise, when reading from a connectionless socket, you must specify the sender's address at each read.

  • Connectionless sockets cannot guarantee that the datagrams will arrive in the same order as they were sent.

You can think of using a connectionless socket as being something like sending a series of letters to a friendeach letter needs to be individually addressed and posted, and there is no guarantee that they will arrive in the exact order in which they were sent (or, indeed, that they will arrive at all!).

Connected sockets, on the other hand, do maintain the concept of a session between the two participantsso the address needs to be specified only once, at first connection, and datagrams will arrive in an ordered fashion. You might think of this as loosely analogous to a telephone callyou dial the person's number only once, at the start of the conversation, and you can guarantee that they will hear everything you say in exactly the order in which you say it! (You should not take this analogy too far, howeverthere are many respects in which a connected socket session is nothing at all like a phone call!) The disadvantage of connected sockets is that they are less efficient to set up and use.

Both connected and connectionless sockets are supported in Series 60, and both use exactly the same classes (for example, RSocket , RSocketServ , and so on). The difference is just in the way the sockets are set up and configured, and in the member functions of RSocket that are subsequently used to send and receive data. The rest of this section focuses on connected sockets, as these are far more frequently used on Series 60. For details of the differences between connected and connectionless sockets programming, refer to the SDK documentation.

Connected Sockets

Connected sockets are much more commonly used than connectionless sockets. For a small overhead in terms of efficiency, connected sockets provide you with a reliable stream of data between two endpoints, freeing the developer to concentrate on the data itself rather than the mechanics of how it is transmitted.

This subsection details how to use connected sockets in Series 60, using example code taken from the IrSockets application shown in Figure 9-2. This is a simple application which sends short text-based chat messages between two devices using infrared sockets. Each device can choose whether to send or receive, so data can be sent in both directions.

Figure 9-2. The IrSockets example.


The example code in this subsection is drawn from the CIrSocketsEngine class in the IrSockets application. The following code segment shows the member data of CIrSocketsEngine that is used in this subsection:

 // data to transmit TBuf8<KFormEdwinMaxSize16>    iSendBuffer; // data to receive TBuf8<KFormEdwinMaxSize16>    iRecvBuffer; /*! @var the actual sockets */ RSocket iSocket; RSocket iServiceSocket; /*! @var the Symbian OS socket server */ RSocketServ iSocketServer; /*! @var DNS name resolver */ RHostResolver iHostResolver; /*! @var The result from the name resolver */ TNameEntry iNameEntry; /*! @var The name record found by the resolver */ TNameRecord iNameRecord; TProtocolDesc iProtocolInfo; TIrdaSockAddr iIrdaSockAddr; TCommsStatus iState; 

TCommsStatus is a bespoke enumerated type defined for this application; it will be discussed in more detail below.

The basic steps performed by the application are as follows :

1.
Connect to the Symbian OS Socket Server.

2.
Open a socket.

3.
Connect the server socket.

4.
Connect the client socket.

5.
Transfer data.

6.
Close the socket and other handles.

These steps are described in detail here:

Connecting to the Symbian OS Socket Server

The first step of any sockets programming for Series 60, whether client or server, is to connect to the Symbian OS Socket Server. This is a simple step compared to the initialization required for serial communicationwith sockets, there is no need to preload drivers, nor to start the server explicitly.

 // Connect to the Symbian OS Socket Server. TInt err = iSocketServer.Connect(); if (err != KErrNone && err != KErrAlreadyExists)    {    User::Leave(err);    } 

Note that all applications that wish to use the Sockets API must connect to the Symbian OS Socket Serverthis is not the same thing as making a connection to a server socket. Applications that contain a client socket must connect to the Symbian OS Socket Server to create the socket, and this client socket would then connect to a server socket, which will typically exist on another device. The terminology can be a little confusing at first!

Now there are just a couple of further steps required. First, as a sanity check, you should make sure that the Socket Server supports at least one protocol (in other words, that one or more PRT plug-in modules are present):

 TUint numProtocols; // Number of protocols the Symbian OS // Socket Server is aware of err = iSocketServer.NumProtocols(numProtocols); User::LeaveIfError(err); 

Finally, you need to obtain information about the protocol you intend to usein this case, IrTinyTP , which is a transport-layer protocol for infrared.

 _LIT(KProtocol, "IrTinyTP"); ... // Find protocol by name and get description TProtocolName protocolName(KProtocol); err = iSocketServer.FindProtocol(protocolName, iProtocolInfo); User::LeaveIfError(err); 

Opening a Socket

Having connected to the Symbian OS Socket Server, you are ready to open a socket. This is done using RSocket::Open() , which takes the following parameters:

  • RSocketServ& aServer a reference to an open socket server session

  • TUint addrFamily a constant value representing an address family (see Table 9-1)

  • TUint sockType a constant value representing a socket type (see Table 9-2)

  • TUint protocol a constant value representing a protocol (see Table 9-3)

Tables 9-1 through 9-3 list the constant values that may be passed to RSocket::Open() . All header files are located in the \epoc32\include folder, relative to the root of your SDK installation.

Table 9-1. Socket Address Family Constants

Constant

Defined in

Description

KAfInet

in_sock.h

Internet sockets

KIrdaAddrFamily

ir_sock.h

Infrared sockets

KBTAddrFamily

bt_sock.h

Bluetooth sockets


Table 9-2. Socket Type Constants

Constant

Defined in

Description

KSockStream

es_sock.h

Reliable (connection-oriented) socket

KSockDatagram

es_sock.h

Unreliable (connectionless) socket

KSockSeqPacket

es_sock.h

(Currently unused)

KSockRaw

es_sock.h

Raw socket


Table 9-3. Socket Protocol Constants

Constant

Defined in

Description

KProtocolInetIcmp

in_sock.h

ICMP Internet Message Control Protocol

(For information on this and other TCP/IP protocols, see the TCP/IP section later in this chapter.)

KProtocolInetTcp

in_sock.h

TCP Transfer Control Protocol

KProtocolInetUdp

in_sock.h

UDP User Datagram Protocol

KProtocolInetIp

in_sock.h

IP Internet Protocol

KIrmux

ir_sock.h

IrMUX

(For information on this and other IR protocols, see the Infrared section later in this chapter.)

KIrTinyTP

ir_sock.h

IrTinyTP

KBTLinkManager

bt_sock.h

Bluetooth Link Manager Protocol

(For information on this and other Bluetooth protocols, see the Bluetooth section later in this chapter.)

KL2CAP

bt_sock.h

Bluetooth Logical Link Control and Adaptation Protocol

KRFCOMM

bt_sock.h

Bluetooth RFCOMM Protocol

KSDP

bt_sock.h

Bluetooth Service Discovery Protocol


These constants may be passed directly to RSocket::Open() . However, as in the IrSockets application, it is best practice to find a protocol by name (as shown in the previous code segment) and get these values from the Sockets Server. The following segment shows how a socket is opened using values obtained from the Sockets Server.

[View full width]
 
[View full width]
TInt err; // Open socket err = iSocket.Open(iSocketServer, iProtocolInfo.iAddrFamily, iProtocolInfo.iSockType, iProtocolInfo.iProtocol); if (err != KErrNone && err != KErrAlreadyExists) { User::Leave(err); }

Again, this step is the same for both client and server sockets code. Note that opening a socket is a slightly confusing term it does not connect it. All you have done here is to initialize a socket object. Connection is shown in the following text, and this is where the code starts to differ between client and server usage.

Connecting the Server Socket

To initialize your socket as a server you need to carry out three steps. First you need to bind it to an address and port number which the client socket will specify in order to connect to it. In IrSockets , this takes place in the CIrSocketsEngine::ConfigureReceiverL() function:

 // Port number const TUint KPortNumber = 0x02; ... // Set port iIrdaSockAddr.SetPort(KPortNumber); // Bind socket locally and set up listening queue iSocket.Bind(iIrdaSockAddr); 

After the socket is bound to an address and port number, you use the Listen() method to make it listen for incoming connections from elsewhere. The KSizeOfListenQueue parameter passed to the Listen() method in the IrSockets example specifies the length of the listen queue in other words, the number of outstanding connection requests that the socket can queue up before further requests are rejected.

In addition, you will need a second socket to service any incoming requests, as your listening socket must stay free to monitor for further incoming connection requests. This is also done in CIrSocketsEngine::ConfigureReceiverL() :

 // Size of Listening Queue const TUint KSizeOfListenQueue = 1; ... // Listen for connection from sender iSocket.Listen(KSizeOfListenQueue); // Create blank socket iServiceSocket.Open(iSocketServer); 

Finally, you need to call Accept() to accept the first incoming request. Note that this request may not yet have been made. However, Accept() is an asynchronous call, so it will eventually complete when a request is received. (Alternatively, it can also be cancelled before this ever happens, using CancelAccept() .) If, on the other hand, a request has already been issued by another device, this will currently be pending in the listen queue that was set up by Listen() , with the result that Accept() will complete very quickly.

In any case, the call to Accept() takes exactly the same form, as follows:

 iSocket.Accept(iServiceSocket, iStatus); iState = EAccepting; SetActive(); 

Note the following interesting points about the preceding code segment:

  • You pass your second socket ( iServiceSocket ) through as a parameter to Accept() , as this is the socket that will actually be used to transfer data, once a connection has been established.

  • The CIrSocketsEngine class is an Active Object, so it passes its own iStatus member to AcceptL() and subsequently calls SetActive() on itself. Its RunL() method will be called when a connection request occurs.

  • The iState member is used to implement a basic state machine within the CIrSocketsEngine class. This is essential, owing to the asynchronous nature of the Sockets APIwhen the RunL() method is called, you must be able to determine what state you are currently in (for example, accepting, reading, writing and so on). iState is a part of the implementation of such a state machine in the CIrSocketsEngine class and is not a part of the Sockets API. Be aware that you may not see exactly this same variable name (or type) in other Series 60 sockets code, although generally it will always implement some kind of state machine along these lines.

While the server just waits for any incoming connection, the client actively pursues a connection. The following discussion shows what is required on the client side in order to make a connection request.

Connecting the Client Socket

At the simplest level, connecting a client socket just involves calling Connect() , specifying the required address and port information. This would be the case, for example, in connecting a TCP/IP socket to an Internet server at a known address and port.

However, for an infrared connection, the address of the target machine is typically not known in advance. Connections are not being made over a permanent, physical network, and so are more ad hoc in nature. The same is true of Bluetooth. In both cases, connection is usually preceded by a phase called discovery , where the device looks for other machines that are in range and offering services (listening on a socket) for a given protocol.

Discovery

The class used to discover other devices is RHostResolver . This is also used with TCP/IP sockets to resolve host names (such as www.emccsoft.com) into numerical IP addresses. This is a quite different process from discovering IR or Bluetooth devices, but it performs a similar role in the process of establishing a socket, and the RHostResolver class ensures a consistent set of APIs across all socket types. An RHostResolver object is opened in a similar way to an RSocket : with an Open() method which takes an open Socket Server session, an address family and a protocol.

As discovery could potentially take a relatively long time, the corresponding API ( RHostResolver::GetByName() ) is asynchronous. As in the example of Accept() above, the CIrSocketsEngine class is itself an Active Object that will issue such asynchronous requests and handle their completion. The code required to start the discovery phase is shown here:

 // Number of devices to search for during discovery const TUint KNumOfDevicesToDiscover = 1; ... TInt err; THostName hostname; // Set up socket for device discovery. // Use one slot, i.e. search for one device. TPckgBuf<TUint> buf(KNumOfDevicesToDiscover); iSocket.SetOpt(KDiscoverySlotsOpt, KLevelIrlap, buf); // Open host resolver for device discovery err = iHostResolver.Open(iSocketServer, iProtocolInfo.iAddrFamily, iProtocolInfo.iProtocol); User::LeaveIfError(err); iState = EDiscovering; iHostResolver.GetByName(hostname, iNameEntry, iStatus); SetActive(); 

This code finishes by calling the asynchronous function iHostResolver.GetByName() to start the discovery process, passing in its own iStatus member and setting itself active. This will result in the RunL() method getting called when discovery is complete. Note that the iState variable is also set accordingly , so that RunL() can correctly determine which request it is handling.

GetByName() , and other asynchronous calls on RHostResolver , can be cancelled by calling RHostResolver::Cancel() .


Connection

When the Active Object handling the call to GetByName() completes successfully, the iNameEntry member will contain the address of a valid server machine that was discovered . This is then used to connect, as follows:

 iHostResolver.Close();  // As it's no longer needed. // Store remote device address iIrdaSockAddr = TIrdaSockAddr(iNameEntry().iAddr); ... iIrdaSockAddr.SetPort(KPortNumber); iState = EConnecting; // Request connection. iSocket.Connect(iIrdaSockAddr, iStatus); SetActive(); 

This code is taken from the CIrSocketsEngine::RunL() and CIrSocketsEngine::ConnectL() methods , but brought together here for the sake of clarity. Note that the KPortNumber passed to SetPort() is the same value used when binding the server socketthis ensures that the client will attempt to connect with the correct port number.

As you might expect, Connect() is another asynchronous function. Note that this may fail even if the discovery phase succeededfor example, if the server has gone out of range since it was discovered.

Transferring Data

Once a socket connection is established between two devices, data transfer is achieved by means of the Read() and Write() methods; note that both functions are available to each socket. In the IrSockets example, only the client writes and only the server reads, but this need not be the casedata may flow either way, or in both directions. (Remember that on the server side, it is a separate socket, iServiceSocket , which performs any reading or writing. The main socket, iSocket , is busy listening for any further incoming connection requests.)

The implication is that there must be an agreement or "contract" between the client and server implementations to ensure that a write on one side is matched by a read on the other. In the case of the IrSockets example, this "contract" is very simple but is specific to the example. It can be summarized as follows:

  • Client writes one descriptor.

  • Server reads one descriptor.

  • Both sockets disconnect.

Of course, in the real world most sockets "contracts" are more complex than this, and in order to be useful they need to be widely understood and implemented. Such contracts become specified as higher-level protocols, such as HTTP and FTP.

The IrSockets application sends a Unicode descriptor from one participant to the other. This is complicated slightly by the fact that sockets can handle only 8-bit descriptors. Hence the application actually externalizes the 16-bit descriptor to an 8-bit one, which it then transfers.

The CIrSocketsEngine::SendL() function shows how the data is externalized to an 8-bit descriptor and then sent over the socket:

 void CIrSocketsEngine::SendL(const TDesC& aData)    {    if (iState == ESending)       {       // Create a TDesBuf referring to iSendBuffer.       // This will allow us to create a stream to stream data       // into iSendBuffer.       TDesBuf buffer;       buffer.Set(iSendBuffer);       // Create a write stream       RWriteStream stream(&buffer);       CleanupClosePushL(stream);       // Stream aData (the 16-bit descriptor argument)       // into iSendBuffer       stream << aData;       // Clean up the stream       CleanupStack::PopAndDestroy();       // Now write the 8-bit descriptor iSendBuffer to the       // socket.       // On the reading side this can then be read in, then       // the 16-bit descriptor internalized from the 8-bit       // buffer.       iSocket.Write(iSendBuffer, iStatus);       SetActive();       }    } 

Note that Write() is an asynchronous function, which is handled using an Active Object in the usual way.

That is all that is required on the writing sidethe next thing that happens will be a call to RunL() with a status value that indicates either success or failure.

On the receiving side, the process is split into two steps:

  • An asynchronous read occurs on the socket which will receive the 8-bit data into the member iRecvBuffer .

  • Once this has completed, code in the RunL() method, which streams the 16-bit descriptor from iRecvBuffer .

First, here is the CIrSocketsEngine::ReceiveL() function which handles reading the 8-bit data from the socket:

 void CIrSocketsEngine::ReceiveL()    {    if (iState == EAccepted)       {       iRecvBuffer.Zero();       iState = EReceiving;       iServiceSocket.Read(iRecvBuffer, iStatus);       SetActive();       }    } 

Note that the Read() function is called on iServiceSocket , not iSocket . Remember that iSocket is in permanent use listening for new connection requests, and iServiceSocket is used to service those requests.

When the Read() request completes, it will result in a call to CIrSocketsEngine::RunL() , with iState set to EReceiving . This is handled as followsyou should be able to see a clear correlation between the steps of this process and those used above in SendL() :

 // Create a TDesBuf object referring to iRecvBuffer    TDesBuf desBuf;    desBuf.Set(iRecvBuffer);    // Open a stream on iRecvBuffer using desBuf    RReadStream stream(&desBuf);    CleanupClosePushL(stream);    // Stream the data into a Unicode buffer    TBuf<EFormEdwinMaxLength> unicodeBuffer;    stream >> unicodeBuffer;    // Clean up the stream    CleanupStack::PopAndDestroy();    // Pass the Unicode descriptor to the UI iIrSocketsEngineObserver.DisplayReceivedDataL(unicodeBuffer); 

Closing Sockets

In order to clean up neatly, you will need to ensure that all your R -class handles are closed. CIrSocketsEngine cancels and closes all sockets and the host resolver in a CloseSubSessions() function, which in turn is called from its DoCancel() method. This ensures that in the event of a cancel, any outstanding asynchronous requests will be correctly cancelled. In its destructor, the class calls Cancel() (following good practice for Active Objects)which will result in a call to DoCancel() , and therefore CloseSubSessions() and also closes the Socket Server session.

These functions are shown below.

 CIrSocketsEngine::~CIrSocketsEngine()    {    Cancel(); // This will close all subsessions    // Close session on Sockets Server    if (iSocketServer.Handle() != 0)       {       iSocketServer.Close();       }    } void CIrSocketsEngine::DoCancel()    {    CloseSubSessions();    } void CIrSocketsEngine::CloseSubSessions()    {    // Cancel and close host resolver    if (iHostResolver.SubSessionHandle() != 0)       {       iHostResolver.Cancel();       iHostResolver.Close();       }    // Cancel and close service socket    if (iServiceSocket.SubSessionHandle() != 0)       {       iServiceSocket.CancelAll();       iServiceSocket.Close();       }    // Cancel and close main socket    if (iSocket.SubSessionHandle() != 0)       {       iSocket.CancelAll();       iSocket.Close();       }    // Set state to idle    iState = EIdle;    } 

It is important that before you make a call to cancel or close any handles, you ensure that the handles have been initialized (using Handle() and SubSessionHandle() ). Failure to do this can result in a panic.

Before closing a socket, you should call RSocket::CancelAll() . This is a convenient function that will cancel any outstanding asynchronous request on a socket, regardless of the original function involved ( Read() , Write() , Accept() and so on)there is no need to check the value of iState here, which makes the CloseSubSessions() function a lot simpler.

Secure Sockets

Secure sockets encrypt the data that passes between the client and server sockets. Series 60 provides support for two secure sockets standards: SSL (Secure Sockets Layer) v3.0, and TLS (Transport Layer Security) v1.0.

SSL is a thin protocol layer, which provides almost completely transparent encryption over a standard connection-oriented socket. After some minimal initialization, an SSL-enabled socket is used in exactly the same way as a nonencrypted socket. In Series 60, SSL can even be enabled on a standard socket after it has been connected. SSL is the mechanism used to implement secure Web pagesany page with a URL beginning https :// has been encrypted using SSL.

TLS operates at the transport layer (usually TCP/IP) and requires slightly more initialization than SSL. TLS is intended as a successor to SSL and can be regarded as "SSL 3.1." However, as SSL is still the prevalent standard on the Internet, this is the default option for secure sockets in Series 60TLS must be enabled explicitly.

It should be noted that SSL and TLS are not interoperable. Also, Series 60 provides support only for client-side secure socketsyou cannot implement an SSL-enabled server using native Series 60 APIs.

Finally, note that in Series 60, only a TCP/IP socket can be secured.

Series 60 1.x Secure Sockets

The APIs used for secure sockets have changed between Series 60 1.x and Series 60 2.x. Both APIs are described here, starting with Series 60 1.x.

Securing a New Socket

In Series 60 1.x, secure sockets programming is performed using exactly the same classes as seen previously in this section: RSocketServ , and RSocket . To enable SSL, all you need to do is call RSocket::SetOpt() with the parameters KSoSecureSocket and KSolInetSSL (defined in in_sock.h ), as shown in the following code segment:

 // RSocketServ iSocketServ; defined in class definition. // Connect iSocketServ... // RSocket iSocket; defined in class definition. // First, open a socket. // This socket must be opened with the KAfInet, // KSockStream and KProtocolInetTcp options // to allow SSL. User::LeaveIfError(iSocket.Open(iSocketServ, KAfInet, KSockStream, KProtocolInetTcp)); // Then, to enable SSL on that socket, call SetOpt() // with the option name of KSoSecureSocket, and the // option level of KSolInetSSL. User::LeaveIfError(iSocket.SetOpt(KSoSecureSocket, KSolInetSSL)); // SSL is now enabled on the socket, and no // further actions are required. // Now the socket can attempt to connect to the // remote host. iSocket.Connect(iInetAddr, iStatus); 

If you wish to set other secure options on the socket, additional calls to SetOpt() can be made before calling Connect() . The various options available for secure sockets are listed on Symbian's Web site ( http://www.symbian.com/developer/techlib/papers/sslapi/sslapi.html).

Securing an Already Connected Socket

You can also enable encryption on a socket that has already been connected, provided it has been opened with the same parameters as shown in the code above (that is, for TCP/IP). To do this, simply call SetOpt() with the KSoSecureSocket and KSolInetSSL parameters. If you need to set any other security options (also with SetOpt() ), be sure to do this first and call socket.SetOpt(KSoSecureSocket, KSolInetSSL) last of all.

Using a Secure Socketthe SSL Handshake

There is an important point to note on the use of secure sockets, regardless of whether you enable security before connecting or vice versa. In both cases you do not receive explicit notification that SSL has been successfully enabled on the socket. Instead, the following happens:

  • Where you enable SSL before connecting the socket, the call to RSocket::Connect() completes once a TCP/IP connection has been made. Only after this does the process of establishing the SSL connectionknown as the SSL handshake begin.

  • When enabling SSL on an already connected socket, the SSL handshake starts when SetOpt(KSoSecureSocket, KSolInetSSL) is called.

In each case, any attempts to read from, or write to, the socket while the handshake is still in progress will be buffered. If the SSL handshake fails, the first read/write operation after the handshake was initiated will complete with an error code. This is your only way of determining if the SSL handshake completed successfully.

Using TLS

TLS is used in a very similar way to SSL, but it requires a little more configuration. Unfortunately, there is no explicitly named function to enable TLS. Instead, it is again enabled by passing particular parameters to SetOpt() , as described below.

A secure socket encrypts data using a mechanism known as a cipher suite . A number of cipher suites are defined within the SSL/TLS standards, and during the SSL handshake the client runs through a list of cipher suites, enquiring whether the server implements them, until one is found which both sides support.

The cipher suites listed in Table 9-4 are available to use with the weak version of the cryptography library; this is included with the public SDK. Those in Table 9-5 are available for use with the strong version of the cryptography library, in addition to those available in the weak library. The strong library is not available in the public SDK, as it is subject to export restrictions under UK law. It may be present in licensee products, depending on legal arrangements. Such arrangements are beyond the scope of this book.

Table 9-4. Weak Cryptography Cipher Suites

Name

Value

SSL_RSA_WITH_DES_CBC_SHA

[0x00][0x09]

SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA

[0x00][0x11]

SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA

[0x00][0x14]


Table 9-5. Strong Cryptography Cipher Suites

Name

Value

SSL_RSA_EXPORT_WITH_RC4_40_MD5

[0x00][0x03]

SSL_RSA_WITH_RC4_128_MD5

[0x00][0x04]

SSL_RSA_WITH_RC4_128_SHA

[0x00][0x05]

SSL_RSA_EXPORT_WITH_DES40_CBC_SHA

[0x00][0x08]

SSL_RSA_WITH_3DES_EDE_CBC_SHA

[0x00][0x0a]

SSL_DHE_DSS_WITH_DES_CBC_SHA

[0x00][0x12]

SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA

[0x00][0x13]

SSL_DHE_RSA_WITH_DES_CBC_SHA

[0x00][0x15]

SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA

[0x00][0x16]


The default ordering of the supported cipher suites is shown in Table 9-6. Note that this is the order when using the strong version of the cryptography library. If the weak version is used, the order remains the same, but suites from the strong cryptography library are not included.

Calling RSocket::SetOpt() with a first parameter of KSoCurrentCipherSuite can change the order of the list of cipher suites which the client sends to the server. Each cipher suite is referred to by a two-byte value, and a list of these is passed to RSocket::SetOpt() . In order to enable TLS, you must make this call and set the last value in the list to [0xff][0xff] .

Table 9-6. Default Order of Cipher Suites

Name

Value

SSL_RSA_WITH_RC4_128_MD5

[0x00][0x04]

SSL_RSA_WITH_RC4_128_SHA

[0x00][0x05]

SSL_RSA_WITH_3DES_EDE_CBC_SHA

[0x00][0x0a]

SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA

[0x00][0x16]

SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA

[0x00][0x13]

SSL_DHE_DSS_WITH_DES_CBC_SHA

[0x00][0x12]

SSL_RSA_WITH_DES_CBC_SHA

[0x00][0x09]

SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA

[0x00][0x11]

SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA

[0x00][0x14]

SSL_RSA_EXPORT_WITH_DES40_CBC_SHA

[0x00][0x08]

SSL_RSA_EXPORT_WITH_RC4_40_MD5

[0x00][0x03]


The following code segment provides an example. Note how the list of values is passed in as a descriptor.

 // Call SetOpt()to enable security on the socket User::LeaveIfError(iSocket.SetOpt(KSoSecureSocket, KSolInetSSL)); // Now call again to set TLS and a preferred // list of cipher suites const TInt KNoOfCiphers = 4; // 2 bytes per cipher suite const TInt KArraySize = KNoOfCiphers * 2; const TUint8 array[KArraySize] =    {    0x00, 0x0a,    // SSL_RSA_WITH_3DES_EDE_CBC_SHA    0x00, 0x04,    // SSL_RSA_WITH_RC4_128_MD5    0x00, 0x16,    // SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA    0xff, 0xff     // to specify TLS    }; // Create a descriptor version of the array TPtrC8 ptr(array, KArraySize); // Call SetOpt(). Note that the second parameter is // still KSolInetSSL, even though we want to use TLS. User::LeaveIfError(iSocket.SetOpt(KSoCurrentCipherSuite, KSolInetSSL, ptr)); // Now the socket can attempt to connect to the // remote host iSocket.Connect(iInetAddr, iStatus); 

As with SSL, Connect() will return as soon as a TCP/IP connection is established. The completion value of the first read or write operation should be checked to determine whether the TLS handshake was successful.


Series 60 2.x Secure Sockets

Series 60 2.x adds some new classes to handle secure sockets. The main one is CSecureSocket , which is instantiated from an RSocket handle to represent a secure socket. Its API is derived from the new mixin interface MSecureSocket .

The main shortcoming from Series 60 1.x that is addressed by the new API is the lack of notification when the secure handshake completes. In Series 60 2.x, the handshake is initiated explicitly by means of an asynchronous function, and the corresponding Active Object is notified when the handshake completes.

Another key difference from Series 60 1.x is that the RSocket handle must be connected before it is secured. So now the sequence of events is always as follows:

  • Open a TCP/IP socket exactly as before, using RSocket::Open() .

  • Connect the socket, again just as in Series 60 1.x, using RSocket::Connect() .

  • Once RSocket::Connect() has completed, create a CSecureSocket object, passing the connected RSocket handle into its NewL() function.

  • Call StartClientHandshake() on your secure socket, and wait for this to complete.

  • Transfer data securely using the Send() and Recv() methods of CSecureSocket .

The following code segment shows construction of a CSecureSocket object and the initiation of the SSL handshake. Assume this is within a class derived from CActive , with member data iSocket ( RSocket ) and iSecureSocket ( CSecureSocket* ). iSocket has been opened and connected as described above.

 // Construct the secure socket object _LIT(KSSL3, "SSL3.0"); // or "TLS1.0" for TLS iSecureSocket = CSecureSocket::NewL(iSocket, KSSL3); // Start the SSL handshake process iSecureSocket->StartClientHandshake(iStatus); SetActive(); 

When the handshake completes, the Active Object's RunL() method will be called, with the value of iStatus indicating whether the handshake was successful. If the handshake succeeded, you can then go on to transfer data as follows:

 // Class has the following members: // - iRequestBuffer, a TBuf8 big enough to contain what we // want to send // - iBytesSent, a TInt // initialize iRequestBuffer here iSecureSocket->Send(iRequestBuffer, iStatus, iBytesSent); SetActive(); 

When the Active Object's RunL() is called, iBytesSent will contain the number of bytes successfully sent from iRequestBuffer . If not all bytes have been sent, you will need to issue further Send() requests until the entire buffer has been transmitted.



Developing Series 60 Applications. A Guide for Symbian OS C++ Developers
Developing Series 60 Applications: A Guide for Symbian OS C++ Developers: A Guide for Symbian OS C++ Developers
ISBN: 0321227220
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net