Description

Service discovery mechanism provide the means for clients to discovery the existence of services provided by hosts as well as the attributes of those services. When searching for services, clients needs to specify the wanted pattern(service attribute characteristics). The pattern is a list of UUIDs used to locate matching service records.

Each service record has an identifier called handle(a 32-bit number) that uniquely identifies each service record within an SDP server. The search procedure consist of two steps:

  1. Retrieve the handles: Using GetRemoteServiceHandles a client can retrieve the handles for the records that implement the wanted UUID (characteristic).
  2. Retrieve the record: GetRemoteServiceRecord/!GetRemoteServiceRecordAsXML returns the record in the binary/XML format.

Those methods are basically a mapping of Service Search (SS) and Service Attribute (SA) transactions. For more details read the Bluetooth core specification. The specification defines a third optional transaction called Service Search Attribute (SSA), it is an unified transaction where the user send the wanted pattern and receive a record list that match the pattern.

Search patterns

The pattern is data element sequence where each element in the sequence is a UUID. Currently, the BlueZ D-Bus API supports only a single pattern. It can be UUID-128 or friendly names. Friendly names are hard-coded in hcid, therefore UUID-128 format is more suitable.

The first UUID defined by the Bluetooth Assigned Numbers document is known as the Bluetooth Base UUID and has the value 00000000-0000-1000-8000-00805F9B34FB. Here are some UUID examples:

  • PNP: 00001200-0000-1000-8000-00805F9B34FB
  • HID: 00001124-0000-1000-8000-00805F9B34FB
  • Headset: 00001108-0000-1000-8000-00805F9B34FB

Sending a empty string the hcid will consider that the client wants a public browse query.

Currently, the supported friendly service names are:

  • vcp: Video Conference
  • pbap: Phone Book Access
  • sap: SIM Access
  • ftp: OBEX File Transfer
  • bpp: Printing
  • bip: Imaging
  • synch: Synchronization
  • dun: Dial-Up Networking
  • opp: OBEX Object Push
  • fax: Fax
  • spp: Serial Port
  • hsp: Headset
  • panu: PAN User
  • nap: Network Access Point

Development warnings/recommendations

TBD: multiple requests, public browse, device class and service class

XML representation

<?xml version="1.0" encoding="UTF-8" ?>

<record>
        <attribute id="0x0000">
                <uint32 value="0x00010013" />
        </attribute>
        <attribute id="0x0001">
                <sequence>
                        <sequence>
                                <uuid value="00005557-0000-1000-8000-0002ee000001" />
                        </sequence>
                </sequence>
        </attribute>
        <attribute id="0x0004">
                <sequence>
                        <sequence>
                                <uuid value="0x0100" />
                        </sequence>
                        <sequence>
                                <uuid value="0x0003" />
                                <uint8 value="0x01" />
                        </sequence>
                </sequence>
        </attribute>
        <attribute id="0x0005">
                <sequence>
                        <sequence>
                                <uuid value="0x1002" />
                        </sequence>
                </sequence>
        </attribute>
</record>

Python examples

import dbus

bus = dbus.SystemBus();

obj = bus.get_object('org.bluez', '/org/bluez/hci0')
adapter = dbus.Interface(obj, 'org.bluez.Adapter')

# Change XX:XX:XX:XX:XX:XX to a valid address, empty string to service brownse
handles = adapter.GetRemoteServiceHandles('XX:XX:XX:XX:XX:XX', '')
for handle in handles:
    # Change XX:XX:XX:XX:XX:XX to a valid address
    print adapter.GetRemoteServiceRecordAsXML('XX:XX:XX:XX:XX:XX', handle)

D-Bus glib example

This is an example function retrieving channel for specified service

gint
get_remote_service_channel (DBusGProxy *adapter_proxy, const gchar *address, const gchar *uuid)
{
	GError			*error = NULL;
	GArray			*handle_array = NULL;
	GArray			*record_array = NULL;
	sdp_record_t		*sdp_record = NULL;
	gint			scanned;
	sdp_list_t		*protos = NULL;
	gint			channel = -1;

	/* find services that match our UUID */
	if (!dbus_g_proxy_call (adapter_proxy, 
				"GetRemoteServiceHandles", &error,
				G_TYPE_STRING, address,
				G_TYPE_STRING, uuid,
				G_TYPE_INVALID,
				DBUS_TYPE_G_UINT_ARRAY, &handle_array,
				G_TYPE_INVALID)) {
		g_warning (error->message);
		g_error_free (error);
		goto out;
	}
	if (!handle_array) {
		goto out;
	}
	
	/* get service record for the first service handle */
	if (!dbus_g_proxy_call (adapter_proxy,
				"GetRemoteServiceRecord", &error,
				G_TYPE_STRING, address,
				G_TYPE_UINT, *((guint32 *)handle_array->data),
				G_TYPE_INVALID,
				DBUS_TYPE_G_UCHAR_ARRAY, &record_array,
				G_TYPE_INVALID)) {
		g_warning (error->message);
		g_error_free (error);
		goto out;
	}
	if (!record_array) {
		goto out;
	}
	sdp_record = sdp_extract_pdu ((uint8_t *)record_array->data, &scanned);
	
	/* get channel for this service */
	if (sdp_get_access_protos (sdp_record, &protos) != 0) {
		goto out;
	}

	channel = sdp_get_proto_port (protos, RFCOMM_UUID);
 out:
 	if (protos) {
		sdp_list_foreach (protos, (sdp_list_func_t)sdp_list_free, 0);
		sdp_list_free (protos, 0);
 	}
 	if (sdp_record)
		sdp_record_free (sdp_record);
	
	return channel;
}

Extracting binary records

Converting from binary format to sdp_record_t

int scanned;
sdp_record_t *record;

...
/* Could be the reply of GetRemoteServiceRecord */
dbus_message_iter_init(msg, &iter);
dbus_message_iter_recurse(&iter, &array);
dbus_message_iter_get_fixed_array(&array, &binary_record, &size);

record = sdp_extract_pdu(binary_record, &scanned);
if (!record) {
        /* Failed */
        ...
}

if (size != scanned) {
        /* Size mismatch of service record */
        ...
}

sdp_record_free(record);

XML to SDP record

The sdp_xml_parse_record function belongs to BlueZ utils helper library

sdp_record_t *record;
char buf[2048];
...

/* Reading the XML file or Received from GetRemoteServiceRecordAsXML and writing to buf */
...

record = sdp_xml_parse_record(buf, strlen(buf));
if (!record) {
        /* Parsing XML failed */
}

sdp_record_free(record);

SDP record to binary format

This is an example how convert from sdp_record_t to binary format.

static int update_record(uint32_t handle, sdp_record_t *rec)
{
        DBusMessage *msg, *reply;
        DBusError derr;
        sdp_buf_t buf;

        if (sdp_gen_record_pdu(rec, &buf) < 0)
                return -1;

        msg = dbus_message_new_method_call("org.bluez", "/org/bluez",
                                        "org.bluez.Database", "UpdateServiceRecord");
        if (!msg)
                return -ENOMEM;

        dbus_message_append_args(msg,
                        DBUS_TYPE_UINT32, &handle,
                        DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
                        &buf.data, buf.data_size,
                        DBUS_TYPE_INVALID);

        ...

        return 0;
}

dbus-send examples

# Public Browse
$dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0 org.bluez.Adapter.GetRemoteServiceHandles string:"00:14:51:58:A5:6C" string:""

# Searching serial port profile 
$dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0 org.bluez.Adapter.GetRemoteServiceHandles string:"00:14:51:58:A5:6C" string:"spp"

# Getting the record
$dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0 org.bluez.Adapter.GetRemoteServiceRecord string:"00:14:51:58:A5:6C" uint32:65540

# Getting the record in the XML format
$dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0 org.bluez.Adapter.GetRemoteServiceRecordAsXML string:"00:14:51:58:A5:6C" uint32:65540