Up: Bluetooth

SDP

The Service Discovery Protocol (SDP) allows the others to find out which services this device offers, and their parameters. For example, a mobile may query a computer for whether it has the SyncML service active for synchronizing phonebook and calendar entry; if so, the result includes the RFCOMM channel.

A service like the others

While SDP looks like having a special status in Bluetooth, still it is a service like the others. For example, it is even possible for a computer with some Bluetooth services active not to have SDP at all, or to have SDP active but some of its services not registered to it.

In both cases, a mobile that tries to send a file to the computer is not able to determine the RFCOMM channel of the service on the computer. There is no default like port 80 in HTTP: the channel varies from device to device, and has to be found using SDP. One may write a program for the mobile to use the specific channel 11, but the default Bluetooth software of the mobiles generally does not allow this.

A server program for a Bluetooth service needs not only to listen for incoming connections, but also to register itself with the SDP service. The Java classes in Android and midlets do this automatically, but the latter does not make the service browsable, as noted below.

Querying SDP

When the user tries to the send a file from the mobile to a computer, what happens is:

                   query UUID=0x1105
          <---------------------------------

                     handle=0x10001
          --------------------------------->
computer                                      mobile
           request attribute handle=0x10001
          <---------------------------------

                       attribute
          --------------------------------->

The mobile extracts the RCOMM channel from that data received from the computer and sends the file on that channel. The same procedure is used for finding out the availability of an arbitrary service on another device and its RFCOMM channel.

Records and attributes

SDP has a record for every service. Each record is a set of pairs attributeID,value. As an example, the record of an OBEX push service may contain the pairs:

0x0000 - 0x00010001
0x0001 - [0x1105]
0x0100 - "OBEX Object Push"

In each pair, the first element is the name of the attribute. In this case, 0x0000 = ServiceRecordHandle, 0x0001=ServiceClassIDList and 0x0100 = ServiceName. Therefore, the record could be rewritten as:

ServiceRecordHandle:	0x00010001
ServiceClassIDList:	[0x1105]
ServiceName:		"OBEX Object Push"

As this example shows, an attribute value may be a number, a sequence (in this case, containing only 0x1105) or a string. Other types are described below.

UUIDs

The number 0x1105 is of particular significance. While it is written as a 16-bit number, it is to be considered as the 128-bit number (0x1105 << 96) | 0x0000000000001000800000805F9B34FB. Equivalently, it is the number 0000110500001000800000805F9B34FB. The reason for such a long sequence of digits is that numbers like this must be universally unique, not just unique in a particular context.

The particular number 0000110500001000800000805F9B34FB identifies the OBEX push service. It is the number itself that identifies the OBEX push service, not the fact that it is in an attribute ServiceClassIDList. In other words, this number should only occur in such an attribute, since it is the identifier of a service.

Universally unique identifiers are abbreviated as UUIDs. The commonly used ones have the form XXXXYYYY00001000800000805F9B34FB, where XXXXYYYY is either a 16-bit or a 32-bit number. In such cases, only YYYY or XXXXYYYY is transmitted over the air, with the implicit intention to represent the full UUID.

Universal uniqueness allows for querying by a UUID without specifying the attribute where the UUID is supposed to occur. For example, if a mobile wants to push a file to a computer, it does not query whether the computer offers a service where 0x1105 (OBEX push) is in the ServiceClassIDList attribute. It only asks for a service containing the UUID 0x1105, since this UUID alone means "the OBEX push service", regardless of the attribute where it is.

The answer to such a query is the ServiceRecordHandle of the record containing the UUID 0x1105, in this case 0x00010001 (this is not a UUID, but just a plain 32-bit number). If no record containing the UUID 0x1105 exists, the answer is negative.

With the handle 0x00010001, a further query can be done: an attribute of a record. In reality, service records are not so short as in the example, and contain about ten attributeID/value pairs. One of the attributes is the ProtocolDescriptorList, which contains the RFCOMM channel, inter alia. Why inter alia? This is explained in a moment.

Values

Values are of various kinds: numbers, UUIDs, sequences, etc. Each value is conceptually (the actual representation may be slightly more complicated) a triple:

+------+--------+-------+
| type | length | value |
+------+--------+-------+

The type may be nil, text, Boolean, URL, integer of 1, 2 or 4 bytes, UUID, sequence or alternative. Within a sequence or alternative each value is represented in the same way (type-length-value). This allows for tree-like values.

The most commonly used tree-like value is for the ProtocolDescriptorList attribute. This is a list of protocols, like L2CAP-RFCOMM-OBEX. The meaning is: this service is based on OBEX, which itself is based on RFCOMM which is based on L2CAP. Each of these three has its own UUID: L2CAP=0x0100, RFCOMM=0x0003 and OBEX=0x0008. As a side note: since these identifier are universally unique, a device can retrieve all services based on RFCOMM just by querying for the UUID 0x0003.

Actually, the ProtocolDescriptorList is not a simple list like [0x0100, 0x0003, 0x0008], meaning [L2CAP, RFCOMM, OBEX]. Rather, it is a list of lists:

[[L2CAP], [RFCOMM,channel], [OBEX]]

If the service uses channel 11, this list is [[0x0100], [0x0003, 11], [0x0008]]. This is not a list of UUIDs. It is a list of lists, each having a UUID as its first element.

Each element of this list represents a protocol. The lowest-level protocol is [L2CAP]. The next is [RFCOMM,11]; the first element is the UUID of the RFCOMM protocol, the second is the channel. The last protocol is [OBEX]. The first element of each list is the UUID of the protocol, the following are additional parameters, if any.

The point of UUIDs is that they are unique even across different meanings. For example, 0x1105 is the OBEX push service, 0x0100 is the L2CAP protocol. They have to be different even if they are used in different contexts. A particularly important UUID is 0x1002, which is neither a protocol nor a service, but a browse group.

BrowseGroupList and PublicBrowseRoot

A device can retrieve the record of a specific service or a list of services by a UUID they contain. For example, all services using RFCOMM are returned by a search for UUID=0x0003, while only the OBEX push service is returned by a search for UUID=0x1105. A Linux tool allows querying a mobile from a computer:

sdptool search --bdaddr 21:34:A2:09:3E:01 0x1105
sdptool search --bdaddr 21:34:A2:09:3E:01 0x0003

The first example connects to the device of BDADDR 21:34:A2:09:3E:01 and requests the handle of a record that contains UUID=0x1105, then uses this handle to request the entire record. The second command does the same with UUID=0x0003, but this time the returned record handles are usually more than one; all records are then requested and printed.

How to request just all SDP records? The SDP protocol search require a UUID, always. It cannot be omitted. Yet, by using a UUID that is supposed to be contained in all records, all records will be retrieved. This is what happens when calling:

sdptool browse 21:34:A2:09:3E:01 

The UUID that is searched by this command is PublicBrowseRoot, the UUID 0x1002. It is supposed to be present in all records, and in particular in the BrowseGroupList attribute. The actual specification is more complicated than this, as it involves a tree of browse groups, but generally the SDP records just contain a BrowseGroupList attribute whose value is a list that contains only the PublicBrowseRoot value. This way, all records can be retrieved just by searching for UUID=0x1002.

When the server side of a service is activated, an SDP record may be automatically created by the Bluetooth software library. The midlet and Android Java classes do this, but only the latter also creates the BrowseGroupList attribute. A midlet has to do it by itself. The following code snippet shows how, where s is the result of Connector.open("btspp://localhost..."):

    ServiceRecord rec = local.getRecord(s);
    DataElement de = new DataElement(DataElement.DATSEQ);
		// 0x1002 = PublicBrowseRoot
    DataElement uuid = new DataElement(DataElement.UUID, new UUID(0x1002));
    de.addElement(uuid);
		// 0x0005 = BrowseGroupList
    rec.setAttributeValue(0x0005, de);
    local.updateRecord(rec);

As a side remark, PublicBrowseRoot is a UUID, abbreviated to 16 bits but representing a 128-bit quantity, while BrowseGroupList is, like all other attributeIDs, just a plain 16-bit number. This means that 0x1002 is guaranteed not to be the UUID of anything else, not even of something different like a protocol or a service. Instead, the only requirement on BrowseGroupList=0x0005 is that no other attribute has the same attributeID, but 0x0005 may very well be equal to the value of some unrelated UUID. In fact, it is: 0x0005 is also the UUID of the deprecated protocol TCS-BIN.