10th August 2006
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.
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.
Stream
supplied by the consumer.
This is as shown the PUT sample shown above.
Stream
returned by the library.
An example of its usage is the PutGuiVb.vb sample.
Stream
supplied by the consumer.
An example of its usage is the GetFolderListing C# sample.
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
.
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).
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 IOException
s and SocketException
s 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 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.
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.
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