The OBEX packet is an array of bytes composed of headers and data. Same headers byte are in Listing 1.
private byte obex_conn = (byte)0x80;
private byte obex_disconn = (byte)0x81;
private byte obex_put = (byte)0x02;
private byte obex_put_final_bit = (byte)0x82;
private byte obex_ok = (byte)0xA0;
private byte obex_continue = (byte)0x90;
private byte obex_body = (byte)0x48;
private byte obex_end_body = (byte)0x49;
private byte obex_version = (byte)0x10;
private byte obex_conn_flags = (byte)0x00;
private byte obex_name = (byte)0x01;
private byte obex_length = (byte)0xC3;
Listing 1. Headers bytes used in OBEX protocol
//mPacketSize is the maximum packet size allowed
public byte[] ConnectPacket(Int16 mPacketSize)
{
MemoryStream ms = new MemoryStream();
byte[] bufxx = new byte[] { obex_conn }; //first byte identify the packet
ms.Write(bufxx, 0, 1);
bufxx = BitConverter.GetBytes((UInt16)(7)); //the length of the packet
ms.Write(bufxx, 0, 2);
bufxx = new byte[] { obex_version };
ms.Write(bufxx, 0, 1);
bufxx = new byte[] { obex_conn_flags };
ms.Write(bufxx, 0, 1);
bufxx = BitConverter.GetBytes((Int16)mPacketSize); // The number Int16 is
ms.Write(bufxx, 0, 2);
// converted in two bytes.
ms.Position = 0;
return ms.ToArray();
}
Listing 2.
The method that generate an array of bytes – the connect packet
In Listing 2 is an example of the program which create a connect packet (byte array). We create a memory stream (MemoryStream ms = new MemoryStream();) and a one dimensional byte array ( byte[] bufxx = new byte[] { obex_conn }; ) in which we store the byte 0x80 (obex connect header). We write the bufxx byte array to the memory stream ( ms.Write(bufxx, 0, 1);). The packet length is 7 bytes. The command ((Int16)(7)) parse this integer to Int16, so the function (BitConverter.GetBytes((Int16)(7))) give us a two byte array bufxx. We write it to the memory stream. The same we do with the maximum packet length. Two one byte arrays with the obex version (0x10) and connection flags are also written to the memory stream. The method return a byte array so the memory stream is converted to it with the function (ms.ToArray()).
public byte[] SuccessConnectPacket(Int16 mPacketSize)
{
MemoryStream ms = new MemoryStream();
byte[] bufxx = new byte[] { obex_ok };
ms.Write(bufxx, 0, 1);
bufxx = BitConverter.GetBytes((Int16)(7));
ms.Write(bufxx, 0, 2);
bufxx = new byte[] { 0x10 };
ms.Write(bufxx, 0, 1);
bufxx = new byte[] { obex_conn_flags };
ms.Write(bufxx, 0, 1);
bufxx = BitConverter.GetBytes((Int16)mPacketSize);
ms.Write(bufxx, 0, 2);
ms.Position = 0;
return ms.ToArray();
}
Listing 3. SuccessConnectPacket that the server send as a response of the connect packet
Listing 3 show the program that create the packet that the server send as a response to the connect packet.
//bufferx is a byte array, in it we read the received bytes. numR is the number of
bytes received (read).
public int DecodeReceivedA0(byte[] bufferx,
int numR)
{
int maxPacketSize = 0;
try
{
//receive connect packet
A0 //
if (bufferx[0] == (byte)0xA0)
byte[] buffer1 = new byte[2];
int
iix = 4;
while (iix < 6)
{
buffer1.SetValue(bufferx.GetValue(iix+1
), iix-4);
iix += 1;
}
maxPacketSize = (int)BitConverter.ToInt16(buffer1,
0);
}
catch (Exception ex)
{
this.error="Error in response packet."
+ ex.Message.ToString();
return 0;
}
return maxPpacketSize;
}
Listing 4.
The method for decoding the received packet (byte array).
When the server or the client receive a packet, they must decode it: read the information in it. Listing 4. show a method for decoding the packet. We read the received bytes in byte array named bufferx. The number of received bytes is numR (numR is less or equal bufferx length). After reading the bytes in the byte array, we extract the first byte. If its value is 0xA0, we know, that this is a response to a connect packet and with the DecodeReceivedA0 method we extract the data from it: packet length, OBEX version; maximum packet size. Decoding the packet we obtain two information: the packet first byte 0xA0, that tell the client that it is connected to the server and the maximum packet size allowed in the transfer process.
Those and other methods for creating OBEX packet are in the TransferSerial class of the project.
Let us see how we send and receive OBEX packet in C#. For example, we send a connect packet and receive a response (ok) packet.
private void sendobex_Click(object sender,
EventArgs e)
{
this.monitor.Text = "";
this.lblMessage.Text = "Connecting...";
this.tra
= new TransferSerial();
if (!tra.Connect(this.serialPort1)) return;
this.com.Text = this.serialPort1.PortName.ToString();
this.serialPort1.Write(tra.conn, 0, tra.conn.Length);
//write the Connect packet
this.Invoke(new LabelWriteByte(this.LogByte),
tra.conn, tra.connLength);
if (!tra.ConnectResponse(this.serialPort1))
{
this.lblMessage.Text = tra.ErrorMes();
return; //didn't receive ok packet
from server
}
this.Invoke(new LabelWriteByte(this.LogByte),
tra.conn, tra.connLength);
this.lblMessage.Text = "Connected.";
this.disconnected = false;
//response connect ok
this.confirm = true;
}
Listing 5. A Click event send a connect packet
and receive an OK from the server
In Listing 5. is called a Connect method to a serial port (!tra.Connect(this.serialPort1)) that generate a byte
array conn in which we store the put packet created with the method in Listing 6.
The command this.serialPort1.Write(tra.conn, 0, tra.conn.Length)
write the array (packet) to the selected comm port serialPort1 stream and the bluetooth stack software transfer this bytes to the server port. The command
tra.ConnectResponse(this.serialPort1)
says the system to listen on port serialPort1 for the response from server. The function decode the received packet and return true if the server response with a OK response packet (Listing 7).
public bool Connect(System.IO.Ports.SerialPort
serialport1)
{
if (serialport1.PortName == "COM0")
{
MessageBox.Show("Select comm port!");
return false;
}
if (!this.OpenSerialPort(serialport1)) return
false; //open a serial port
try
{
this._conn = this.ConnectPacket();
//create a connect packet
this._connLength = this._conn.Length;
}
catch (Exception ex)
{
MessageBox.Show("Can not connect
to remote service. (Remote server not listen?)." + this.ErrorMes() + ex.Message.ToString());
return false;
}
return true;
}.
Listing 6. Connect method
The Connect method (Listing 6) open a selected comm port and call the ConnectPacket metod (Listing 2.), that generate the put packet and store its bytes in a byte array. It returns also a put packet length (connLength).
//read response first 6 byte from connect
packet
public bool ConnectResponse(System.IO.Ports.SerialPort serialport1)
{
if (!this.ReadResponsePacket6(serialport1)) //read response from server
{
MessageBox.Show("Did not received
response Ok from server.");
return false;
}
return true;
}
Listing 7. ConnectResponse method that wait
on port serialport1 the response from
the server
The ConnectResponse method (Listing 7) wait on port serialport1 the response from the server.
private bool ReadResponsePacket6(System.IO.Ports.SerialPort
serialport1)
{
while (serialport1.BytesToRead < 3) Thread.Sleep(10);
byte[] ComBuffer1 = new byte[7];
int lenRead = serialport1.Read(ComBuffer1, 0,
7);
//extract the packet length (byte 2 and 3 of the
readed bytes)
byte[] bufferPacket = new byte[2];
int iix = 0;
while (iix < 2)
{
bufferPacket.SetValue(ComBuffer1.GetValue(iix
+ 1), iix);
iix += 1;
}
int packetLenToRead
= ((int)BitConverter.ToInt16(bufferPacket, 0)) ;
this._connLength = lenRead; //return the packet
length to the calling procedure
if (packetLenToRead
== 3 && ComBuffer1[0] == obex_ok)
// ok without max packet length
{
this._conn = ComBuffer1;
return true;
}
if (lenRead < 6)
{
lenRead = serialport1.Read(ComBuffer1,
3, 4);
this._connLength
= 3 + lenRead; //return to the calling procedure
}
else
{
this._connLength
= lenRead;
}
if (ComBuffer1[0] == obex_ok)
{
//client listen response from server
int pr = this.DecodeReceivedA0(ComBuffer1,
6);
if (pr > 0)
//max packet length >0
{
this.mPacketSize
= Convert.ToInt16(pr); //max packet
size
//read the
remaining bytes of the packet
if (packetLenToRead
> 7)
{
lenRead
= serialport1.Read(ComBuffer1, 7, packetLenToRead - 6);
this._connLength
+= lenRead;
}
this._conn = ComBuffer1;
//return to the calling procedure
return true;
}
else
{
MessageBox.Show("Not
connected. Error in response packet.Try again.");
return false;
}
}
else
{
return false;
}
}
Listing
8. A method for reading and decoding the received packet.
The ReadResponsePacket6 method read the port stream and decode its
first 3 (6) bytes (Listing 8). The
command bufferPacket.SetValue(ComBuffer1.GetValue(iix
+ 1), iix);
write the byte with index iix+1 of the byte array
ComBuffer1 to the byte with the index iix of the byte array
bufferPacket and the command ((int)BitConverter.ToInt16(bufferPacket,
0)) convert the two byte array to an integer – the packet length. If
the lenRead is less than 6 and packet length is grater than 3, we read the remaining
bytes (lenRead = serialport1.Read(ComBuffer1, 3, 4);).
In the line int pr = this.DecodeReceivedA0(ComBuffer1,
6); we decode the first 6 bytes and from this we extract the maximum
packet length.
Later, we shall explain the delegate LabelWriteByte.
In the development and test phase of the project we register the process steps in the log file. We will also write the bytes traffic (sent and received) on comm port to the file logByte and monitor this traffic in the text box named 'monitor' on the form.
// logByteFileName is the name of the log
byte file, for example C:\myByteLog.txt
private void LogByte(byte[] bWrite, int byteLen)
{
if (fLogByte == null) fLogByte = new FileStream(logByteFileName, FileMode.Create,
FileAccess.Write);
fLogByte.Write(bWrite, 0, byteLen);
//Only in the test phase. Write the byte array
in hexadecimal format
string hexString = newLine + newLine; //separete
with a blank line
int jj = 0;
while (jj < byteLen)
{
hexString += String.Format("{0:X2}",
bWrite[jj]); //write the bytes in string
format
if (hexString.Length > 80) //the nunmer of columns
don't excede 80.
{
this.monitor.Text
+= hexString;
hexString = "";
}
jj += 1;
}
if (hexString.Length > 0)
{
this.monitor.Text += hexString; //write the string to the monitor textbox.
}
}
Listing 9. A method for writing a byte array
to the file and textbox on the form.
On every read or write of bytes on the comm port, we write the bytes to the logByte file and monitor text box. This operation is executed with the form invoke method and the delegate LabelWriteByte (Listing 9). For example the command
this.Invoke(new LabelWriteByte(this.LogByte),
ComBuffer1, lenRead)
use the delegate LabelWriteByte to call the logByte method to write the first lenRead bytes of the ComBuffer1 byte array to the file and textbox.
Let us look at the command in the logByte method:
hexString += String.Format("{0:X2}",
bWrite[jj]); //write the bytes in string
format
The string hexString show us a byte array in hexdecimal format (as we see the bytes in the file binary editor).
Figure 1. Bytes traffic on comm port sending a file of small size.
In the simple project SimpleTranConnect we use the methods described till now. It can be downloaded. In the Figure 1. are presented the visual controls used in the project. We add to it also the serial port component that is not visible. In the textbox we see the hex representation of the bytes traffic on the comm port. The first sequence of bytes (80070010000004) is the connect packet sent to the server. Its length is 7 bytes. The response packet is the second sequence (A0070010000004) etc. The last sequence of bytes (810300) is an disconnect packet with the length of 3 bytes. If you would like to test it, you have to install the FileTranServer (see the download).
When the file is small enough to put it in one packet, we send the file in one put packet. The body header of this packet is 0x82. Normally the body header is byte 0x02, only the body header of the last packet is 0x82. When the server receive the put packet with body header 0x02 it respond with the continue packet and when it receives body header 0x82, it respond with OK packet with the header 0xA0.
private void sendobex_Click(object sender,
EventArgs e)
{
this.monitor.Text = "";
this.progressBar1.Value = 0;
this.lblMessage.Text = "Connecting...";
this.tra = new TransferSerial();
if (!tra.Connect(this.serialPort1)) return;
this.com.Text = this.serialPort1.PortName.ToString();
this.serialPort1.Write(tra.conn, 0, tra.conn.Length);
//write the Connect packet
this.Invoke(new LabelWriteByte(this.LogByte),
tra.conn, tra.connLength);
if (!tra.ConnectResponse(this.serialPort1))
{
this.lblMessage.Text = tra.ErrorMes();
return; //didn't receive ok packet
from server
}
this.Invoke(new LabelWriteByte(this.LogByte),
tra.conn, tra.connLength);
this.lblMessage.Text = "Connected.";
file = tra.FileStreamToSend(this.destination.Text.Trim());
if (file == null) return;
this.fileName = tra.fileName;
this.destName = this.destination.Text.Trim();
//response connect ok
try
{
//receive connect packet
A0 and send put
// if (bufferx[0]
== (byte)0xA0)
tra.CreatePutPacketAndSend(serialPort1,
this.destName, this.fileName, this.file, this.streamRead);
this.serialPort1.Write(tra.conn,
0, tra.connLength);
streamRead = streamRead + tra.chunkReadFile;
if (file.Length - streamRead >
0) this.transfering = true;
}
catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message.ToString());
}
Listing
10. The button click event for sending small files.
In Listing 10 we try
to connect to the server with the click on the button. If the connection (tra.Connect(this.serialPort1)
is successful, we create the file stream (file) of the file we wish to send ( file=this.FileStreamToSend()).
The method tra.CreatePutPacketAndSend create the put packet.
We send it with
this.serialPort1.Write(tra.conn,
0, tra.connLength);
If the file length in bytes is smaller or equal to bytes read, we call the DisconetService method, otherwise the control of the program execution is passed to the serial port datareceived event.
public byte[] PutPacket(string destName, string
fileName, FileStream file, int streamRead)
{
// int chunk = 1024;
int chunk = this.packetSize-nameF.Length-16;
//calculate the chunk of file bytes to read
if (file.Length < this.packetSize -nameF.Length-16)
chunk = (int)file.Length;
if (file.Length - streamRead < chunk) chunk
= (int)file.Length - (int)streamRead;
//read from file
byte[] fiReadChunk = new byte[chunk];
int
chunkR= file.Read(fiReadChunk, 0, chunk);
byte[] nameF = new UnicodeEncoding().GetBytes(destName);
//in the byte array nameF we store the destination
file name
//sum of the header length (in revers order).
Look at bufxx.Write!
int packetLength = (1 + chunkR + 2 + 1) + (4
+ 1) + (2 + nameF.Length + 2 + 1) + 2;
// chunkR+ nameF.Length+16
MemoryStream ms = new MemoryStream();
byte[] bufxx;
if (file.Length - streamRead - chunkR <= 0)
{
bufxx = new byte[] { obex_put_final_bit
};
}
else
{
bufxx = new byte[] { obex_put };
}
ms.Write(bufxx, 0, 1);
//packet length
bufxx = BitConverter.GetBytes((UInt16)(packetLength));
ms.Write(bufxx, 0, 2);
bufxx = new byte[] { obex_version };
ms.Write(bufxx, 0, 1);
//name header
bufxx = new byte[] { obex_name };
ms.Write(bufxx, 0, 1);
bufxx = BitConverter.GetBytes((UInt16)(nameF.Length
+ 3 + 2));
ms.Write(bufxx, 0, 2);
ms.Write(nameF, 0, nameF.Length);
//null terminated
bufxx = new byte[] { 0x00, 0x00 };
ms.Write(bufxx, 0, 2);
//
object length header
bufxx = new byte[] { obex_length };
ms.Write(bufxx, 0, 1);
bufxx = BitConverter.GetBytes((file.Length));
ms.Write(bufxx, 0, 4);
//body header
if (file.Length - streamRead - chunkR == 0)
{
bufxx = new byte[] { this.obex_end_body
};
}
else
{
bufxx = new byte[] { obex_body
};
}
ms.Write(bufxx, 0, 1);
// bufxx = BitConverter.GetBytes((UInt16)(fiReadChunk.Length
+ 3));
bufxx = BitConverter.GetBytes((UInt16)(chunkR
+ 3));
ms.Write(bufxx, 0, 2);
//
bufxx = fiReadChunk;
ms.Write(fiReadChunk, 0, chunkR);
//END of Body
ms.Position = 0;
this._chunkReadFile = chunkR;
return ms.ToArray();
}
Listing11. The C# program that create the
put packet
The central part of the method CreatePutPacketAndSend is the method PutPacket (Listing 11) of the class TransferSerial. This method create a put packet with header 0x02 or 0x82 for the last packet. With the put packet we send the file data and other information. It contains also the headers and the data for the file length, destination file name and the packet size. In the last phase of the procedure (Listing 11) we create the body header and the file data that we read from the file stream. With the variable streamRead we control the portion of the read file stream. Usually the last put packet is of smaller size. In it we put the remaining bytes to send. We send the packet bytes stream (conn) to the comm port with the command serialPort2.Write(conn, 0, conn.Length).
In Figure 1. we see the sent and received bytes on the virtual serial port in hexadecimal format. There are the results of the connect, transfer and disconnect process sending the file with contents »ABCDANDABCD«. Its hexadecimal representation is »41424344414E4441424344«. The destination file name is c:\jumar.txt. The code for this project is in the downloadable file SimpleTranFile.zip.
When the file is of larger size, we send it in a sequence of put packets. How we send the first packet is described in the previsious section.
private serialPort1_DataReceived(object sender,
System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (this.transfering)
{
//Receiving packet from server
and sending put packet
bool cont =tra.Receive(this.serialPort1
);
if
(this.disconnected)
{
this.Invoke(new LabelWrite(this.LabelMes),
">>Client received success 0xA0 when at end of file and disconnected. File
length:" + this.file.Length.ToString() + " read:" + this.streamRead.ToString());
}
else
{
this.Invoke(new LabelWrite(this.LabelMes),
">>Client received success 0xA0 when at end of file. ");
}
this.Invoke(new LabelWriteByte(this.LogByte),
tra.conn , tra.connLength);
if (!cont)
{
this.Invoke(new LabelWrite(this.LabelMes),
"End file transfer." );
this.DisconnectService();
return; //didn't
receive a continue packet
}
//1) received confirm of
connection 80 or put packet send
//
this.confirm = true;
this.Invoke(new LabelWrite(this.LabelMes),
">>Client recceive continue (packet 0x90) and send new put packet." + this.file.Length.ToString()
+ " read:" + this.streamRead.ToString());
tra.CreatePutPacketAndSend(this.serialPort1
,this.destName, this.fileName, this.file, this.streamRead);
this.serialPort1.Write(tra.conn,
0, tra.connLength);
streamRead = streamRead + tra.chunkReadFile;
this.Invoke(new LabelWriteByte(this.LogByte),
tra.conn, tra.connLength);
}
}
Listing 12. The serial port DataReceived event
The second and next packets traffic are controlled by the event serialPort1_DataReceived (Listing 12). We read the bytes received on the comm port with the method Receive(), which return a boolean value cont (inue). If the value is true (we receive at the comm port the packet continue), we send the next packet, otherwise we terminate the transfer.
The method CreatePutPacketAndSend create a put packet and store it in a byte array conn. We write this array to the comm port (we send it to the server). The event serialPort1_DataReceived wait for the next received byte. (With the modification of the presented programs in this method you can control also the connect and first put packets).
To improve performance in the production code of your file transfer project, you should exclude the monitor part of the presented programs. You should build the client and the server function in the same project to have the possibility to send and receive files on booth devices. We separate the functions for pedagogical reason.
We don't comment the server side project FileTranServer. In it we use the same technique as in the client project.
It is possible to hide the complexity of encoding, decoding and transferring packet over bluetooth serial ports in helper class and we can transfer and receive files with only a few line of code as is shown in the events sendobex_Click and serialPort1_DataReceived.
SimpleTranConnect.zip , SimpleTranFile.zip , FileTranClient.zip , FileTranServer.zip
By Viraj Chatterjee, July 24, 2002.
Elements of the OBEX
protocol.
A Simple Guide To Mobile Phone File Transferring
By hesicong