OBEX library — Programmer’s guide

10th August 2006

Introduction

The library can be used at two levels. At the upper level it provides full support for all client-side OBEX operations, including Put, Get, SetPath, Delete, and Abort. There is currently no support for server-side operation, nor for reliable sessions. At a lower lever it provides full general parsing and creating of OBEX PDUs (packets); this usage should be rare however and won’t be discussed further here.

The library also provides a parser for the OBEX Folder Listing objects.

Client-side operations

An short example may be most explanatory. The following PUTs a file to an IrDA peer.

Imports System
Imports System.IO 'e.g. FileStream, FileMode, etc.

Imports Brecham.Obex

Imports InTheHand.Net 'e.g. IrDAEndPoint
Imports InTheHand.Net.Sockets 'e.g. IrDAClient
' Available from http://32feet.net/.


Class VbPutSampleSample
   Public Shared Sub Main(ByVal args() As String)
      ' Open file as selected by the user
      If args.Length <> 1 Then
         Console.WriteLine("No filename given.")
         Exit Sub
      End If
      Dim filename As String = args(0)
      Dim srcFile As New FileStream(filename, FileMode.Open, FileAccess.Read)
      ' Connect
      Dim cli As New IrDAClient("OBEX")
      Dim sess As New ObexClientSession(cli.GetStream, 4096)
      sess.Connect
      ' And Send
      Dim name As String = Path.GetFilename(filename)
      Dim contentLength As Int64 = srcFile.Length
      sess.PutFrom(srcFile, name, Nothing, contentLength)
      cli.Close
   End Sub
End Class

That is of course lacking any exception handling but is otherwise complete. The other operations are called in a similar fashion. The supported operations are as follows.

Connect
Sends a connection requests to the peer. Can be used to select a particular service / application on the peer, for instance the Folder Browsing Service. It also exchanges the maximum receive sizes with the peer, without which a maximum send size of 255 bytes will be used by both ends. It also checks that the peer is using the same version of the OBEX protocol as us — though version 1.0 alone is defined currently. Its use is optional.
Disconnect
Closes the session. Its use is optional.
Put/from
Uploads an object to the peer, with the library reading the content from a Stream supplied by the consumer. This is as shown the PUT sample shown above.
Put
Uploads an object to the peer, with the consumer writing to a Stream returned by the library. An example of its usage is the PutGuiVb.vb sample.
Get/to
Downloads an object from the peer, with the library writing the content to a Stream supplied by the consumer. An example of its usage is the GetFolderListing C# sample.
Get
Downloads an object from the peer, with the consumer reading from a Stream returned by the library. An example of its usage is the GetFolderListing C# sample, it is also used internally by the GetFolderListing method on ObexClientSession.
Delete
Deletes an object on the peer, can be a file or a folder.
SetPath
Changes the current folder on the peer. As well as changing to a named child folder, it can also be used to back up one level, or reset to the default folder. An example of its usage is the FolderExplorerVb and GetFolderListing samples.
Abort
Aborts the current operation. This is the only operation that it is legal to call whilst another operation is in progress.

Metadata

All OBEX protocol information is carried in headers, both the information describing the objects being transferred and the data itself. Common headers are Name, Type, Length, and Body and EndOfBody carrying the data itself. Most of the operations (Put, Get, SetPath etc) have a core method that take a collection of headers, as an instance of the class ObexHeaderCollection, but there are generally also more user-friendly overloads that take various combinations of individual header values (for Name, Type, Length etc).

For instance the Put operation has four overloads:

public ObexPutStream Put(ObexHeaderCollection headers);
public ObexPutStream Put(String name, String type);
public ObexPutStream Put(String name, String type, UInt32 length);
public ObexPutStream Put(String name, String type, Int64 length);

The most suitable overload can be chosen depending on the what information is known about the object. Also, in most cases a null value is accepted if a particular value is not known. For instance in that case, both name and type will accept null / Nothing. Some cases do require a header value, for instance void SetPath(String folderName) obviously requires a non-null value.

The Length header is generally optional and can hold only a 32-bit unsigned integer so if a larger value is supplied is will not be included in the request. The Int64 overload exists mainly to accept the value returned by Stream.Length, but also for non-CLS Compatible environments (i.e. where UInt32 is not supported).

Error handling

If the server rejects or cannot handle an operation it will return an error response code. On any unexpected response code the library throws a ObexResponseException, which contains the response code received and any attached description, they are combined in the Message it produces. For instance if the user on a Windows PC rejects a Put the ObexResponseException will contain the following message, where the library has added a textual translation of the numerical code:

Unexpected OBEX response code: 0xC3 (Forbidden).

There is one case where a ObexResponseException is initiated locally, that’s in Connect where the peer server reports no support for the requested service / application. A response code of 0xFE is used in that case.

Any fundamental misbehaviour from the peer, for instance illegal length fields in the response PDU, will cause a ProtocolViolationException to be thrown. Any known minor misbehaviours have been allowed for though — for instance the Wireless Link application for IrDA OBEX in Windows sends a Success code in cases where it should send a Continue.

The library does not catch any exception on accessing the Stream to the peer server, and thus any IOExceptions and SocketExceptions etc will be forwarded to the consumer. The only other exception defined by the library is ObexCreateTooLongException and should not occur when using the session interfaces.

Finally, if Aborting a currently active command then that command can throw an exception on being interrupted. Depending on the state the operation is in the exceptions produced can include ObjectDisposedException and InvalidOperationException.

Asynchronous operations and timeouts

Asynchronous forms of some of the operations are provided, for instance the PutStream and GetStream forms of the Put and Get operations support BeginWrite / EndWrite and BeginRead / EndRead respectively. However currently there aren’t asynchronous forms of the initiating methods i.e. Put, and Get, nor are there asynchronous versions of other methods e.g. PutFrom / GetTo / SetPath etc.

The calling application can of course call the methods asynchronously, whether in a Background Worker component or by manually using a thread-pool thread, or through delegates’ BeginInvoke feature. As noted above, one can ask the server to cancel any operation by calling Abort, in this case from the main thread.

See the PutGuiVb and FolderExplorerVb samples for examples of calling the library from a Background Worker component. The PutGuiVb sample updates an progress bar control as the file is downloaded, and also provides a “Cancel” button to allow the user to cancel the download. The example below includes a fragment of that program, it shows the work done by the background worker thread, that is copying the file content to the peer, and updating the progress status as it goes.

Sub DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
Handles backgroundWorker1.DoWork
  Dim args As BgWorkerArgs = CType(e.Argument, BgWorkerArgs)
  Dim buffer(1023) As Byte
  Dim count As Int32
  Dim updatePeriod As New TimeSpan(0,0,0,0, 250)
  Dim lastProgress As DateTime = DateTime.UtcNow
  Dim elapsed As TimeSpan
  label1.Text = "Sending PUT content..."
  Try
    While(True)
      If (backgroundWorker1.CancellationPending)
        e.Cancel = True
        args.Connection.ObexClientSession.Abort("User cancelled")
        Exit While
      End If
      count = args.Source.Read(buffer, 0, buffer.Length)
      If(count = 0)
        Exit While
      End If
      args.putStream.Write(buffer, 0, count)
      ' Progress reporting; rate-limited.
      elapsed = DateTime.UtcNow - lastProgress
      If (elapsed > updatePeriod) Then
        Dim percentage As Int32 = CType((100.0 * args.Source.Position) / args.Source.Length,Int32)
        backgroundWorker1.ReportProgress(percentage)
        lastProgress = DateTime.UtcNow
      End If
    End While
  Finally
    args.PutStream.Close
    args.Source.Close
    ' If we wanted to do another Put to the same peer we wouldn't close
    ' the session and its underlying network stream here.
    args.Connection.ObexClientSession.Dispose
  End Try
End Sub

See the PutGUI sample for an example of calling a library method asynchronously, using delegate.BeginInvoke to run the operation and a callback method to complete the operation. In summary the code is of the form shown below.

delegate void PutFromNtiCaller(Stream source, String name, String type, Int64 length);

private void button1_Click(object sender, EventArgs e)
{
   …
   state.m_putCaller = new PutFromNtiCaller(sess.PutFrom);
   AsyncCallback cb = new AsyncCallback(PutCompleted);
   state.SetStartTime();
   IAsyncResult ar = state.m_putCaller.BeginInvoke(
   state.m_progressStream, putName, null, state.m_fileStream.Length,cb, state);
   …
}

void PutCompleted(IAsyncResult ar)
{
   …
   // Get the result of the Put operation. This doesn't need to
   // be on the UI thread, but the rest does...
   state.m_putCaller.EndInvoke(ar);
   this.labelStatus.Text = "PutFrom took: " + state.Elapsed.ToString();
   …
}

If one wants to monitor the progress of the self-contained PutFrom or GetTo operations one way would to to create a new Stream type that follows the ‘Decorator Pattern’ and simply passes all read / writes onto the actual stream whilst counting the number of bytes transferred. See the PutGUI sample for an example of such code.

There is also no explicit support for timeouts in the library, but this will be considered in the future based on feedback. At the moment the calling application will have to cancel an operation if it it taking too long. Depending on the type of timeout required one could set a timeout on the Stream or Socket or by synchronizing with the asynchronous invocation of the operation and calling Abort if it is taking too long. Note however if the server on the peer fails to respond to the Abort command then again no timeout will occur. To guard against this one might consider setting a timeout before calling Abort. One can set a communications timeout using NetworkStream.ReadTimeout, Socket.ReceiveTimeout, or ultimately with

Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, timeout);

for instance.

OBEX objects

The OBEX protocol defines a number of object types, these include Folder Listing, Capability, and Object Profile. The library provides a Folder Listing parser which returns the items included in the folder-listing object. The simplest way to use it is to use the GetFolderListing method on ObexClientSession, it returns an ObexFolderListing object which provides two lists, one of the folders and another of the files in the current folder. The example here shows a simple example of displaying the folder listing on the console. To change the current folder use the SetPath methods.

Sub DisplayCurrentFoldersListing(sess As ObexClientSession)
   Dim listing As ObexFolderListing = sess.GetFolderListing
   If (listing.HasParentFolder) Then
      Console.WriteLine("<DIR> ..")
   End If
   For Each folder As ObexFolderItem In listing.Folders
      Console.WriteLine("<DIR> {0}", folder.Name)
   Next
   For Each file As ObexFileItem In listing.Files
      Console.WriteLine(" {0}", file.Name)
   Next
End Sub

The parser can also be accessed directly through the ObexFolderListing object in the Brecham.Obex.Objects namespace. This for instance allows one to access the individual items as they arrive over the network instead of only all items being returned once the end of the listing is received.

Finally, the DTDs for both the Folder Listing and Capability XML objects are included in the library as resources and can thus be accessed directly or with the supplied XmlResolver (ObexXmlResolver) for use in your own application's XML parsers.

Connecting to the peer OBEX server

As noted previously the library itself does not handle creaing a connection to a peer device and the OBEX server there. However a library that handles this is included in the samples. It includes three classes, one to handle connections in a GUI application, one for console menu-driven applications like GetFolderListings, and one that accepts a URI of a similar format to that used by 32feet.NET’s ObexWebRequest class.

They are all based on that same set of subclasses which implement connection to a peer device, to an OBEX server, and disconnecting and cleaning up. Disconnect and clean-up is implemented using the IDisposable interface, that is through calling a Dispose method. Once connected the connected ObexClientSession instance is available through a propertyof the same name.

So examples of their usage would be as the following.

Dim toBluetooth As Boolean = …the state of a radio button selecting Bluetooth / IrDA…
Dim toFolderBrowsingService As Boolean = …a programmer set value perhaps…
Dim conn As New GuiObexSessionConnection(toBluetooth, toFolderBrowsingService, label1)
' …use the connection… e.g.
conn.ObexSessionConnection.SetPath("images")
conn.ObexSessionConnection.Put(fileSource, "logo.gif", Nothing)
conn.Dispose

or

using(ConsoleMenuObexSessionConnection conn = new ConsoleMenuObexSessionConnection()){
  // …use the connection…
  // Dispose is called by the 'using' block
}

See the various sample projects for more samples of usage.

As implied by the samples above the GuiObexSessionConnection constructor takes two flags: whether to connect to Bluetooth or to IrDA, and whether to connect to the OBEX Folder Browsing service or to the default Inbox service. It can also take a Windows Forms control on which status text will be displayed. The choice of Bluetooth device is made by the user from the Windows Bluetooth devices dialog, and since there’s no equivalent for IrDA it currently just chooses the first device.

The ConsoleMenuObexSessionConnection asks the user which protocol, device, and which of the two OBEX services to connect to on the console, and also displays any status messages there too.

Finally, as noted above the Uri ObexSessionConnection constructor takes a URI in a similar format to that used by the ObexWebRequest class, differing only in that it allows not path to be set, for instance obex:/12345678/ instead of obex:/12345678/filename.vcf.

As noted above the source is included to allow extension or modification if the supplied class do not suit your environment. There are three abstract class below the three concrete classes already described. The inheritance tree is as shown here. We should not that these classes are an appendix to the main library and have not been tested as thoroughly as the library’s code. However that are used in most of the sample programs and have been seen to work there.

If creating a new concrete class for an OBEX session connection then inherit from the ObexSessionConnection class, providing overrides of the ChooseService, ChooseProtocol, ChoosePeer, and ShowStatus methods. The other sources will show examples of what each method should do.

Andy Hume