Up: Bluetooth

Bluetooth in Linux with bluezlib and openobex

Bluezlib and openobex are the most commonly used Bluetooth libraries for Linux.

Inquiring

The BDADDR of a mobile can be found by running the following on a computer, if the mobile is visible (temporarily or not):

hcitool inquiry

The result is a "bare" inquiry: nothing else is done over the air but an inquiry. No paging and no request for friendly names, contrary to what mobiles usually do; only the inquiry. The result is only what is obtained from the inquiry: the BDADDR of the devices, their clock and their class (CoD).

Running hcidump in another terminal shows what is actually done: the computer requests an inquiry with an HCI command; the Bluetooth USB dongle communicates that the command is accepted with an HCI event; for each device found an HCI event is sent back to the computer with the BDADDR, clock and class of the device; the completion of the inquiry is then communicated via a further HCI event.

An inquiry can be performed in C using the hci_inquiry function. It requires as an argument an hci_dev_info integer, which can be found by hci_get_route. The program hci_scan.c includes both an inquiry and a request for friendly names for each device found. Yes, {0x33, 0x8b, 0x9e} is magic.

Finding a service

Connecting to a service requires finding it with SDP. For example, before sending a file to a mobile, the computer has to find out whether the mobile offers an OBEX push service for receiving it. This can be done by querying for the UUID of the OBEX push service, 0x1105:

sdptool search --bdaddr 21:4E:27:00:91:EA 0x1105

The result may be something like:

Class 0x1105
Searching for 0x1105 on 21:4E:27:00:91:EA ...
Service Name: OBEX Object Push
Service RecHandle: 0x10001
Service Class ID List:
  "OBEX Object Push" (0x1105)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 9
  "OBEX" (0x0008)
Language Base Attr List:
  code_ISO639: 0x656e
  encoding:    0x6a
  base_offset: 0x100
Profile Descriptor List:
  "OBEX Object Push" (0x1105)
    Version: 0x0100

This is the only service record containing the UUID 0x1105. It gives some information about it, and in particular that it can be accessed through the RFCOMM channel 9. The openobex tool obexftp allows sending a file by:

obexftp -b 21:4E:27:00:91:EA -B 9 -U none -H -S -p Image_2391.jpg

This command requires the BDADDR of the receiver (in this case, 21:4E:27:00:91:EA), the channel (9) and the file name. The mobile asks the user whether to accept it, unless it is configured to automatically accept files from the computer. In the latter case, it just beeps and shows a message ("file received", or similar).

Finding the channel in C requires a number of steps:

What makes these steps complicated to program is the representation of data. UUIDs are structures, so 0x1105 needs to be converted into such a structure. The search for records is in general done on a list of UUIDs; even if the UUID is only one, still a list has to be created and inserted this UUID. Since more than one record may contain the UUID (this would for example be the case for UUID=0x0003, which gives all services based on the RFCOMM protocol), the value returned from a search is a list of record handles even if it only contains one element. Attributes may be searched in ranges, so what returns is a list of attributes. Fortunately, the very common search for the RFCOMM channel in a protocol list has a convenience function that saves from the hassle of scanning the list.

The program sdpchannel.c contains some functions for performing such a search. They return the RFCOMM channel in the last record that matches the given UUID. To compile: cc -DMAIN -lbluetooth sdpchannel.c -o sdpchannel

The RFCOMM channel is required for connecting to an RFCOMM service, including the ones that use the OBEX protocol.

Registering a service

A Linux box can also be used for receiving files, or more generally for offering services. This requires registering a record in the local SDP server sdpd:

sdptool add OPUSH

This command requires the service to be specified by name; for example, OPUSH is OBEX push (0x1105). The other services that can be registered this way are in the man page.

The records currently registered with the SDP service of the local Linux box can then be checked using the same tool:

sdptool browse local

The result is something similar to searching the OBEX push service record on a mobile, with its service class list and protocol list:

Browsing FF:FF:FF:00:00:00 ...
Service Name: OBEX Object Push
Service RecHandle: 0x10000
Service Class ID List:
  "OBEX Object Push" (0x1105)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 9
  "OBEX" (0x0008)
Profile Descriptor List:
  "OBEX Object Push" (0x1105)
    Version: 0x0100

Doing this in C requires building the entire record a piece at time. As for reading an SDP record, several things are lists even if they contain a single element, and UUIDs have to be converted from numbers to structures. A connection to the local SDP service has to be established before registering the record and terminated afterwards.

The sdpregister.c shows an example of registering an OBEX push service in C. Compile with cc -DMAIN -lbluetooth sdpregister.c -o sdpregister

Pairing

Connecting to a mobile usually requires pairing. This is obtained by running an appropriate PIN server program on the computer when attempting to connect. On older bluez versions, this is either one of the following two commands, where the first requires specifying the BDADDR of the mobile:

passkey-agent 1234 21:4E:27:00:91:EA
passkey-agent 1234

On newer versions:

simple-agent

The mobile has to be visible, and an inquiry run from the computer even if the BDADDR of the mobile is already known. At the first connection attempt from the computer, the mobile asks the user for the PIN, in this case 1234.

RFCOMM

The rfcomm program allows connecting a file like /dev/rfcomm0 to the RFCOMM service of a remote device. For example, if the mobile 21:4E:27:00:91:EA offers the dial-up networking service on the RFCOMM channel 1, one may run:

rfcomm connect /dev/rfcomm0 21:4E:27:00:91:EA 1

Everything that is written to /dev/rfcomm0 goes the dial-up service of the mobile, and vice versa. For example, the device type can be interrogated by sending "ATI4". This can be done with standard shell commands (lines marked by the prompt '>' are typed by the user, the rest are answers):

> stty -F /dev/rfcomm0 -echo
> cat /dev/rfcomm0 &
[1] 8414
> echo ATI4 > /dev/rfcomm0
XDRP_00021
OK
> kill 8414

The expect and empty program are useful when writing scripts to interact over such a serial line.

Doing the same in C is not much different from connecting on TCP/IP. For both, the procedure is:

Of course, AF_BLUETOOTH is used instead of AF_INET, and rfcomm address structures instead of ip address structures. The following snippet shows the three steps. The check of the result values is omitted only for the sake of clarity. The value of bdaddr is obtained from a string, but could also be a result of a previous inquiry.

	int channel = 1;
	int sockd;
	struct sockaddr_rc local, remote;
	bdaddr_t bdaddr;

	sockd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

	local.rc_family = AF_BLUETOOTH;
	bacpy(&local.rc_bdaddr, BDADDR_ANY);
	local.rc_channel = 0;
	bind(sockd, (struct sockaddr *) &local, sizeof(local));

	remote.rc_family = AF_BLUETOOTH;
	str2ba("21:4E:27:00:91:EA", &bdaddr);
	bacpy(&remote.rc_bdaddr, &bdaddr);
	remote.rc_channel = channel;
	connect(sockd, (struct sockaddr *) &remote, sizeof(remote));

Once connected, the socket can be read from and written to. The standard fdopen function turns the socket into a stream that can be accessed with fscanf and fprintf.

OBEX: tools

OBEX is a protocol for exchanging objects. It runs over an RFCOMM channel, which can be found via SDP. Different services use OBEX: OBEX file transfer, OBEX push, SyncML, phonebook access, imaging. What they have in common is that objects are sent in one direction or the other.

If a mobile has the OBEX file transfer service, then files can be put and get from it using the obexftp tool. For example, the file Image_0283.jpg in the folder Photos of the mobile can be downloaded by:

obexftp -b 21:4E:27:00:91:EA -c Photos -g Image_0283.jpg

Other options allow for putting (-p) and deleting (-k) files and for listing (-l) and changing (-c) directory. The program is also able to send files to the OBEX push service, but does not detect its RFCOMM channel, so it has to be found manually (OPUSH is the same as 0x1105, the UUID of the OBEX push service):

sdptool search --bdaddr 21:4E:27:00:91:EA OPUSH
# the output of sdptool contains the channel number, for example 12
obexftp -b 21:4E:27:00:91:EA -B 12 -U none -H -S -p Image_2391.jpg

The channel (12 in the example) is found from the output of sdptool, if the OBEX push service is available on the mobile.

A similar tool, obexftpd, acts as an OBEX file transfer server, allowing other devices to connect with the computer for getting and putting files. For this to work, the SDP server sdpd has to be active on the computer.

OBEX in C

Programming with OBEX in C requires understanding how the protocol works. One device sends an object, the other answers with another object, the first sends yet another, and so on. This allows for exchanging data in both directions, but the first device that is always in charge of driving the protocol.

Every object contains a number fields and headers. Contrary to intuition, fields are of little interest to the programmer, and are mostly filled in automatically by the library. The actual data is transmitted in the headers. For example, when sending a file to a mobile, both the name and the content of the file are headers of an object.

Structure

A complete run of OBEX is something like:

The part that is not obvious is how to wait for the answer objects. Rather than by a blocking read-like function, the program calls the library function OBEX_HandleInput to deal with incoming data, and if some is available a callback function is called. The program specifies the callback function at the beginning. The sequence above is implemented as:

OBEX_Init(callback_function)

BtOBEX_TransportConnect

OBEX_ObjectNew
OBEX_ObjectAddHeader
OBEX_ObjectAddHeader
OBEX_ObjectAddHeader
OBEX_Request
while(an event indicating completion is received by callback_function)
  OBEX_HandleInput

OBEX_ObjectNew
OBEX_ObjectAddHeader
OBEX_ObjectAddHeader
OBEX_Request
while(an event indicating completion is received by callback_function)
  OBEX_HandleInput

...

OBEX_TransportDisconnect

OBEX_Cleanup

An object to send is created by a call to OBEX_ObjectNew and multiple calls to OBEX_ObjectAddheader. Then, OBEX_Request sends it. The function OBEX_HandleInput takes care of received data, calling the callback function when an object is received.

More precisely, the callback function may be called with only a part of an object. This is because large objects may be split. One of the arguments of the callback function (event) indicates whether some part of the object is still to be received (event==OBEX_EV_PROGRESS). The program has to call OBEX_HandleInput until the callback function is called with event!=OBEX_EV_PROGRESS.

The program and the callback interact through a structure. It is passed to OBEX_Init as its second argument, and the callback can access it by OBEX_GetUserData. This structure should contain at least a field indicating whether the callback function has been called with event!=OBEX_EV_PROGRESS.

Objects: type and headers

Various kind of objects exist: CONNECT, DISCONNECT, PUT, GET, SETPATH and others (technically, the type is a field of the object). Every transaction begins by sending a CONNECT object. Then, for example, a PUT object can be used to send a file and DISCONNECT to finish. Every object is created of a given type, and initially contains no header.

obex_object_t object;
...
object = OBEX_ObjectNew(handle, OBEX_CMD_PUT);

The handle is the return value of OBEX_Init. Most objects are useless when just created, and need to be added some data. For example, if the name of a file to transfer is in the variable name, the length of the name in namelen, its content in data and its size in size, these have to be added as headers:

obex_headerdata_t header;

header.bs = name;
OBEX_ObjectAddHeader(handle, object, OBEX_HDR_NAME, header, namelen, 0);

header.bs = content;
OBEX_ObjectAddHeader(handle, object, OBEX_HDR_BODY, header, size, 0);

The file name and its content are both byte sequences, so header.bs is used. Other headers are chars or 4-byte integers, and require respectively header.bq1 and header.bq4. Byte sequence header.bs is used for both generic data (like the body of the file) and strings (like the name of the file). Strings are assumed to be Unicode, and their length include the terminating zero. The OBEX_CharToUnicode function is useful for obtaining both a string and its length.

Some headers of interest:

TARGET (byte sequence)
used only in a CONNECT object, which is sent once at the beginning; is an array identifying the specific service implemented atop OBEX: file browsing service (fbs), SyncML, etc.; not to be used in OBEX push
CONNECTION (byte sequence of four bytes)
a number that identifies the connection; used for some services (fbs, SyncML), it is the answer to the CONNECT object, and has to be included as the first header in all subsequent objects sent
TYPE (byte sequence containing a NULL-terminated ascii string)
this has nothing to do with the type of the object; it is used to identify the type of the file that is being sent, e.g., text/v-calendar for a calendar entry
NAME (unicode string)
the name of the file that is requested or being sent
BODY (byte sequence)
the file content, or more generally the actual data

When transmitted, objects may be split on header boundaries. On sending this is done automatically by the library function OBEX_Request, but affects the receiving callback function. Indeed, that function may be called with only some of the headers of the object. An header is never split, but some objects may contain multiple headers of the same kind. This allows sending a large file: its content is stored in a number of BODY headers of the same object.

A complete example

The version of obexpush in the obexpush directory has only two parameters: the BDADDR of the recipient and the file to send. It uses the function for determining the RFCOMM channel from the UUID of OBEX push, then connects to that channel, send a CONNECT object and then an OBEX_CMD_PUT object with the file name and content; finally, a DISCONNECT object is sent, and the connection is closed.

OBEX, server side

An OBEX service is realized by calling BtOBEX_ServerRegister instead of BtOBEX_TransportConnect. The callback function gets called twice for each received object, one with event=OBEX_EV_REQHINT and one with OBEX_EV_REQ. This allows setting the answer with OBEX_ObjectSetRsp at the first call. An example is an OBEX push service for a single file: obexserver.c in the obexpush directory.

OBEX, other transports

OBEX can be used not only over Bluetooth but also IRDA and TCP/IP, just by replacing the BtOBEX_TransportConnect or BtOBEX_ServerRegister functions with their Ir… and Tcp… versions. Also USB can be used, with OBEX_InterfaceConnect.