OBEX. Transfer files between a Pocket PC and a Palm or a Symbian device
Viraj Chatterjee (viraj_chatterjee@yahoo.com), July 24, 2002.
Problem
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:
- Connect
- Put [0 - n packets] /* Receive the data of the file, Name shall be sent by the client in the first packet */
- Final Put /* Receive the last part of the file */
- 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
/*
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:
- Connect
- Put /* Send the name of the file */
- Put [0 - n packets] /* Send the data of the file */
- Final Put /* Send the last part of the file */
- 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:
- http://www.codeproject.com/ce/IrdaMobile.asp
Article: Infrared Communication with your Mobile Phone
-
http://www.pocketpcdn.com/libraries/sapphireirda.html
Control: Sapphire IrDA
- http://www.irda.org/standards/specifications.asp
Article: The OBEX specification at the IrDA site (http://www.irda.org/standards/pubs/IrOBEX1p2_Plus_Errata.zip)
- http://www.irda.org/standards/pubs/IrOBEX1p2_Plus_Errata.zip
Article: Info. regarding Palm Creator ID is mentioned in the OBEX_Errata Compiled.doc file in the this zip
- http://www.calsoft.co.in/techcenter/network/Infrared.html
Article: Programming with Infrared sockets - White Paper