News | Articles | Libraries | Developer Tools | Books | Forum Links | Search   
Sections:
 

OBEX. Transfer files between a Pocket PC and a Palm or a Symbian device

By Viraj Chatterjee, July 24, 2002.
Print version

Problem

OBEX Communication between Pocket PC and Symbian Nokia 7650 How to programmatically transfer arbitrary files or data between a device running the Pocket PC OS and a device with the Palm OS or one with the Symbian OS? Since all these devices support Infrared, we could exploit the IrDA stack for the transfers. However, these devices also need to talk using the same protocol, else one won't understand what the other is talking about. The Palm and the Symbian devices utilize the OBEX protocol for IR transfers, however, some Pocket PC devices, particularly the ones that are the pre Pocket PC 2002 devices, do not support the OBEX protocol. This article deals with this problem. The code snippet can be used for all Pocket PC devices and it will basically simulate the OBEX protocol, and make believe the Palm and the Symbian (OBEX compliant devices) devices that they are compatible.

Problem reframed

How to make the Palm or the Symbian devices feel that they are talking with an OBEX compliant device when data transfer takes place between these devices?

Please note that if you're not too bothered about Pocket PC 2000 devices, you have a no. of readymade solutions that you can utilize in the Pocket PC 2002 OS. You can use the beaming facilities in the device itself. Beam a file from inside File explorer or use the irsquirt program (in the \Windows folder) or use the Pocket PC 2002 SDK to do it from inside your program.

Below is the code snippet. The code is pretty self-explanatory. It isn't modular and isn't the best written ever, since I wanted to stress more on the concept. I have tried to insert some comments to help you understand what I'm doing. However, to understand how OBEX behavior has been simulated, how one can interpret the incoming packet and how one can construct a response packet, please refer the OBEX specification. The specification is available at the IrDA site. The example code below demonstrates a minimal OBEX implementation for receiving and sending files from a Pocket PC to an OBEX compliant device. I suggest readers use this as a guide. For a robust product, the complete OBEX specification should be implemented.

Sub problem 1

How to receive files from an OBEX compliant device on a Pocket PC device?

An OBEX client when trying to send data would try to establish connection with a socket server that has a classname OBEX and attributes IrDA:TinyTP. We set these attributes for the server socket and wait for data from the client. Once the packet data is received, it is understood using the OBEX standard specifications and an appropriate response is sent to the client. A normal sequence of operations when receiving data from a client is:

  1. Connect
  2. Put [0 - n packets] /* Receive the data of the file, Name shall be sent by the client in the first packet */
  3. Final Put /* Receive the last part of the file */
  4. Disconnect

Also note that this code will not work if you have infrared beaming enabled in your Pocket PC 2002 device. Since we can't have two servers providing the same service on one device. So, if you want this code to run on a Pocket PC 2002 device, uncheck the "receive all incoming beams" option in your Beam settings dialog.

You can also download this code from: code1.cpp.

#include <af_irda.h> /* You can experiment with some values for the buffer size in which you receive the data from the socket. I've experienced situations where a certain value like 10K has worked well while debugging step by step, however hasn't worked when the program runs without a breakpoint. A small value like the default OBEX packet size of 255 bytes is inefficient for large object transfers. */ #define MAX_BUFF_SIZE 0x0400 // 1K = 1024 Bytes #define MAX_FILE_NAME 0x0100 // 256 Bytes // OBEX Operation codes #define OBEX_PUT (BYTE)0x02 #define OBEX_PUT_FINAL (BYTE)0x82 #define OBEX_CONNECT (BYTE)0x80 #define OBEX_DISCONNECT (BYTE)0x81 // OBEX Header codes #define OBEX_NAME (BYTE)0x01 #define OBEX_LENGTH (BYTE)0xC3 #define OBEX_DESCRIPTION (BYTE)0x05 #define OBEX_PALM_CREATOR_ID (BYTE)0xCF #define OBEX_BODY (BYTE)0x48 #define OBEX_END_OF_BODY (BYTE)0x49 // OBEX Response codes #define OBEX_CONTINUE (BYTE)0x90 #define OBEX_SUCCESS (BYTE)0xA0 #define OBEX_VERSION (BYTE)0x10 #define OBEX_CONNECT_FLAGS (BYTE)0x00 #define FAILURE_MESG(x) \ {\ wsprintf (szError, x _T(" failed. Error: %d"), WSAGetLastError ());\ MessageBox (NULL, szError, _T("Error"), MB_OK);\ } static void CreateNewFile(BYTE* dataBuff, bool put_final); void ReceiveFile(void) { WSADATA wsaData; WORD wVersion = MAKEWORD(1,1); // Winsock Version 1.1 TCHAR szError[100]; if (WSAStartup(wVersion, &wsaData) != 0) { FAILURE_MESG(_T("WSAStartup")); WSACleanup(); return; } SOCKET ServSock; SOCKET ClientSock; if ((ServSock = socket(AF_IRDA, SOCK_STREAM, 0)) == INVALID_SOCKET) { FAILURE_MESG(_T("socket")); WSACleanup(); return; } // buffer for IAS set BYTE IASSetBuff[sizeof (IAS_SET) - 3 + IAS_MAX_ATTRIBNAME]; int IASSetLen = sizeof (IASSetBuff); PIAS_SET pIASSet = (PIAS_SET) &IASSetBuff; SOCKADDR_IRDA ServSockAddr = {AF_IRDA, 0, 0, 0, 0, "OBEX"}; memcpy(&pIASSet->irdaClassName[0], "OBEX", 5); // include length of the NULL character in the end memcpy(&pIASSet->irdaAttribName[0], "IrDA:TinyTP", 12); pIASSet->irdaAttribType = IAS_ATTRIB_STR; pIASSet->irdaAttribute.irdaAttribUsrStr.Len = 12; if (setsockopt(ServSock, SOL_IRLMP, IRLMP_IAS_SET, (const char *) pIASSet, IASSetLen) == SOCKET_ERROR) { FAILURE_MESG(_T("setsockopt")); closesocket (ServSock); WSACleanup(); return; } if (bind(ServSock, (const struct sockaddr *) &ServSockAddr, sizeof(SOCKADDR_IRDA)) == SOCKET_ERROR) { FAILURE_MESG(_T("bind")); closesocket (ServSock); WSACleanup(); return; } // Establish a socket to listen for incoming connections. // The backlog parameter is currently limited (silently) to // 2. (API Reference) if (listen (ServSock, 2 /* backlog */) == SOCKET_ERROR) { FAILURE_MESG(_T("listen")); closesocket (ServSock); WSACleanup(); return; } // Accept a connection on the socket. if ((ClientSock = accept (ServSock, 0, 0)) == INVALID_SOCKET) { FAILURE_MESG(_T("accept")); closesocket (ServSock); WSACleanup(); return; } // Stop listening for connections from clients. closesocket (ServSock); bool bDisconnectClient = false; int iOffset = 0, totalBytesRead = 0, packetLen = 0; BYTE dataBuff[MAX_BUFF_SIZE]; // Receive data from the client. while (!bDisconnectClient) { if ((iOffset = recv (ClientSock, (char*)dataBuff + iOffset, sizeof (dataBuff) - iOffset, 0)) == SOCKET_ERROR) { FAILURE_MESG(_T("recv")); closesocket (ClientSock); WSACleanup(); return; } unsigned long arg; if (ioctlsocket( ClientSock, FIONREAD, &arg ) == SOCKET_ERROR) { FAILURE_MESG(_T("ioctlsocket")); closesocket (ClientSock); WSACleanup(); return; } if (arg > 0) // some data left to be collected { totalBytesRead += iOffset; iOffset = totalBytesRead; // adjust offset, so that next write // happens at the correct place continue; } else totalBytesRead = iOffset = 0; // Interpret the packets received at the socket, // parse for the appropriate operation // opcodes, and then to make the client believe it is // talking to an OBEX compliant device, // construct headers for appropriate response... // Info. about what an OBEX packet contains for various // operations, refer to the OBEX // specification at the IrDA site. if (dataBuff[0] == OBEX_CONNECT) { dataBuff[0] = OBEX_SUCCESS; *((unsigned short *)&dataBuff[1]) = htons((unsigned short)7); dataBuff[3] = OBEX_VERSION; dataBuff[4] = OBEX_CONNECT_FLAGS; *((unsigned short *)&dataBuff[5]) = htons((unsigned short)MAX_BUFF_SIZE); packetLen = 7; } else if (dataBuff[0] == OBEX_PUT) { CreateNewFile(dataBuff, false); dataBuff[0] = OBEX_CONTINUE; *((unsigned short *)&dataBuff[1]) = htons((unsigned short)3); packetLen = 3; } else if (dataBuff[0] == OBEX_PUT_FINAL) { CreateNewFile(dataBuff, true); dataBuff[0] = OBEX_SUCCESS; *((unsigned short *)&dataBuff[1]) = htons((unsigned short)3); packetLen = 3; } else if (dataBuff[0] == OBEX_DISCONNECT) { dataBuff[0] = OBEX_SUCCESS; *((unsigned short *)&dataBuff[1]) = htons((unsigned short)3); packetLen = 3; bDisconnectClient = true; } if (send (ClientSock, (char*)dataBuff, packetLen, 0) == SOCKET_ERROR) { FAILURE_MESG(_T("send")); closesocket (ClientSock); WSACleanup(); return; } } // Close the client socket, cleanup and exit closesocket (ClientSock); WSACleanup(); return; } static void CreateNewFile(BYTE* dataBuff, bool put_final) { // most of the header and value pairs are of the form... // Byte -> 0 1 2 3... // | Header ID | Length of Header | Value... // | | and Value | static FILE* fp = NULL; if (!put_final && (fp == NULL)) { wchar_t fileName[MAX_FILE_NAME]; // extract the name of the file // skip the first 3 bytes, i.e. PUT opcode + 2 bytes of Length of the packet if (dataBuff[3] == OBEX_NAME) { /* extract the length of the name */ int len_name = ntohs(*(unsigned short *)&dataBuff[4]) - 3; wchar_t* szFileName = (wchar_t *)malloc(len_name); /* Now convert the unicode text from network to host byte order */ for (int i=0; i < (len_name/2); i++) szFileName[i] = ntohs(*(unsigned short *)&dataBuff[6 + 2*i]); // we're using unicode functions instead of the generic functions // 'cos the filename in the OBEX headers is in unicode... // we'll save the file in the "My Documents" folder... wcscpy(fileName, _T("\\My Documents\\")); wcscat(fileName, szFileName); free(szFileName); } fp = _wfopen( fileName, _T("w")); _setmode(fp, _O_BINARY); } /* parse the OBEX header received till we reach the BODY header */ BYTE* buff = dataBuff; int total_bytes, skip = 0; while ((*buff != OBEX_BODY) &&(*buff != OBEX_END_OF_BODY)) { if ((*buff == OBEX_PUT) || (*buff == OBEX_PUT_FINAL)) { total_bytes = ntohs(*(unsigned short *)(buff + 1)); skip += 3; } else if ((*buff == OBEX_NAME) || (*buff == OBEX_DESCRIPTION)) skip += ntohs(*(unsigned short *)(buff + 1)); else if ((*buff == OBEX_LENGTH) || (*buff == OBEX_PALM_CREATOR_ID)) skip += 5; // both occupy 4 bytes..., exceptions to the diagram above buff = dataBuff + skip; if (skip == total_bytes) break; } if ((*buff == OBEX_BODY) || (*buff == OBEX_END_OF_BODY)) { int size = ntohs(*(unsigned short *)(buff + 1)); if (fp) fwrite(buff + 3, size - 3, 1, fp); if (put_final || (*buff == OBEX_END_OF_BODY)) { /* close the file and set the pointer to NULL */ if (fp) fclose(fp); fp = NULL; } } }

Sub problem 2

How to send files from a Pocket PC to an OBEX compliant device?

In this case, the Pocket PC device is the client that sends data and the OBEX server is the Palm or the Symbian device or our OBEX compliant device. Since we know that an OBEX server exists on these devices, we shall first try and query the device about the LSAPSel value of the OBEX server and then connect our socket at that address. Once the connection has been established, data would be sent to the server. A normal sequence of operations when sending data from a client is:

  1. Connect
  2. Put /* Send the name of the file */
  3. Put [0 - n packets] /* Send the data of the file */
  4. Final Put /* Send the last part of the file */
  5. Disconnect

You can download this code: code2.cpp

#define CLASS_NAME "OBEX" #define ATTRIBUTE_NAME "IrDA:TinyTP:LsapSel" #define NAME_LEN(x) (strlen(x) + 1) #define CLASS_NAME_LEN NAME_LEN(CLASS_NAME) #define ATTRIBUTE_NAME_LEN NAME_LEN(ATTRIBUTE_NAME) #define FILE_NAME _T("Hello.txt") #define FILE_TO_SEND _T("\\My Documents\\Hello.txt") #define MAX_PKT_SIZE 0x800 #define MAX_RECV_BUFF_LEN 0x100 #define MAX_SEND_BUFF_LEN 1030 void SendFile(void) { /* Code for sending data to an OBEX compliant device */ WSADATA wsaData; WORD wVersion = MAKEWORD(1,1); // Winsock Version 1.1 TCHAR szError[100]; if (WSAStartup(wVersion, &wsaData) != 0) { FAILURE_MESG(_T("WSAStartup")); WSACleanup(); return; } SOCKADDR_IRDA iraddr; PDEVICELIST pDL; char szServiceName[12]; char cDevice[0x100]; int cnt; BYTE IASQueryBuff[sizeof(IAS_QUERY) - 3 + IAS_MAX_ATTRIBNAME]; int IASQueryLen = sizeof(IASQueryBuff); PIAS_QUERY pIASQuery = (PIAS_QUERY) &IASQueryBuff; SOCKET ClientSock; if ((ClientSock = socket(AF_IRDA, SOCK_STREAM, 0)) == INVALID_SOCKET) { FAILURE_MESG(_T("socket")); WSACleanup(); return; } /* Search for the first device in range. Query that device for the OBEX LSAPSel identifier and connect using that. */ memset (cDevice, 0, sizeof (cDevice)); int nSize = sizeof (cDevice); // loop 10 times, if not found in 10 attempts then there's a problem for (cnt = 0 ; cnt < 10 ; cnt++) { if (getsockopt(ClientSock, SOL_IRLMP, IRLMP_ENUMDEVICES, cDevice, &nSize) == SOCKET_ERROR) { FAILURE_MESG(_T("getsockopt")); closesocket (ClientSock); WSACleanup(); return; } else pDL = (PDEVICELIST)cDevice; if (pDL->numDevice != 0) // Found at least one device break; Sleep(200); } if (cnt == 10) // If there isn't any device in range, exit { FAILURE_MESG(_T("Device discovery")); closesocket (ClientSock); WSACleanup(); return; } // Query IAS database // irdaClassName and irdaAttribName are case sensitive memcpy(&pIASQuery->irdaDeviceID, pDL->Device[0].irdaDeviceID, 4); memcpy(&pIASQuery->irdaClassName, CLASS_NAME, CLASS_NAME_LEN); memcpy(&pIASQuery->irdaAttribName, ATTRIBUTE_NAME, ATTRIBUTE_NAME_LEN); if (getsockopt(ClientSock, SOL_IRLMP, IRLMP_IAS_QUERY, (char *)pIASQuery, &IASQueryLen) == SOCKET_ERROR) { FAILURE_MESG(_T("getsockopt")); closesocket (ClientSock); WSACleanup(); return; } sprintf(szServiceName, "LSAP-SEL%d", pIASQuery->irdaAttribute.irdaAttribInt); memset (&iraddr, 0, sizeof(iraddr)); iraddr.irdaAddressFamily = AF_IRDA; memcpy (iraddr.irdaDeviceID, pDL->Device[0].irdaDeviceID, 4); memcpy (iraddr.irdaServiceName, szServiceName, strlen(szServiceName) + 1); for ( cnt = 0; cnt < 10 ; cnt++ ) { ( connect(ClientSock, (struct sockaddr *)&iraddr, sizeof (iraddr)) == SOCKET_ERROR ) ? Sleep(200) : break; } if (cnt == 10) { FAILURE_MESG(_T("connect")); closesocket (ClientSock); WSACleanup(); return; } BYTE dataBuff[MAX_SEND_BUFF_LEN]; BYTE recvBuff[MAX_RECV_BUFF_LEN]; dataBuff[0] = OBEX_CONNECT; *((unsigned short *)&dataBuff[1]) = htons((unsigned short)7); dataBuff[3] = OBEX_VERSION; dataBuff[4] = OBEX_CONNECT_FLAGS; *((unsigned short *)&dataBuff[5]) = htons((unsigned short)MAX_PKT_SIZE); if (send(ClientSock, (const char*)dataBuff, 7, 0) == SOCKET_ERROR) { FAILURE_MESG(_T("send")); closesocket (ClientSock); WSACleanup(); return; } if ((recv(ClientSock, (char *)recvBuff, MAX_RECV_BUFF_LEN, 0) == SOCKET_ERROR) || (recvBuff[0] != OBEX_SUCCESS)) { FAILURE_MESG(_T("recv")); closesocket (ClientSock); WSACleanup(); return; } wchar_t* wfn = FILE_NAME; size_t fileLen = ((wcslen(FILE_NAME)) + 1) * sizeof(wchar_t); dataBuff[0] = OBEX_PUT; *((unsigned short *)&dataBuff[1]) = htons((unsigned short)(fileLen + 6)); dataBuff[3] = OBEX_NAME; *((unsigned short *)&dataBuff[4]) = htons((unsigned short)(fileLen + 3)); /* Now convert the unicode file name text from host to network byte order */ for (int i=0; i < (int)(fileLen/2); i++) *((unsigned short *)&dataBuff[6 + 2*i]) = htons((unsigned short)wfn[i]); if (send(ClientSock, (const char*)dataBuff, fileLen + 6, 0) == SOCKET_ERROR) { FAILURE_MESG(_T("send")); closesocket(ClientSock); WSACleanup(); return; } if ((recv(ClientSock, (char *)recvBuff, MAX_RECV_BUFF_LEN, 0) == SOCKET_ERROR) || (recvBuff[0] != OBEX_CONTINUE)) { FAILURE_MESG(_T("recv")); closesocket(ClientSock); WSACleanup(); return; } // send the data in a loop, read the file FILE* fp = _wfopen(FILE_TO_SEND, _T("r")); _setmode(fp, _O_BINARY); dataBuff[0] = OBEX_PUT; dataBuff[3] = OBEX_BODY; size_t count = 0; /* Read and send 1K chunks of data to the device */ while ((count = fread(&dataBuff[6], 1, 0x400, fp)) != 0) { if (count < 0x400) { dataBuff[0] = OBEX_PUT_FINAL; dataBuff[3] = OBEX_END_OF_BODY; } *((unsigned short *)&dataBuff[1]) = htons((unsigned short)(count + 6)); *((unsigned short *)&dataBuff[4]) = htons((unsigned short)(count + 3)); if (send(ClientSock, (const char*)dataBuff, count + 6, 0) == SOCKET_ERROR) { FAILURE_MESG(_T("send")); break; } if ((recv(ClientSock, (char *)recvBuff, MAX_RECV_BUFF_LEN, 0) == SOCKET_ERROR) || ((dataBuff[0] == OBEX_PUT) && (recvBuff[0] != OBEX_CONTINUE)) || ((dataBuff[0] == OBEX_PUT_FINAL) && (recvBuff[0] != OBEX_SUCCESS))) { FAILURE_MESG(_T("recv")); break; } } fclose(fp); // An OBEX disconnect should be called here, left as this code is anyway a prototype :-) // Cleanup closesocket(ClientSock); WSACleanup(); return; }

Related resources:

Discuss

Discuss this article. Here you can write your comments and read comments of other developers.
Rate this article:     Poor Excellent    
 12345 
© 2001-2005 Pocket PC Developer Network, a division of Spb Software House