The process of searching for services involves two steps - detecting all
nearby devices with a device inquiry, and connecting to each of those devices
in turn to search for the desired service. Despite Bluetooth's piconet
abilities, the early versions don't support multicasting queries, so this must
be done the slow way. Since detecting nearby devices was covered in Section 4.1, only the second step is described here.
Searching a specific device for a service also involves two steps. The first
part, shown in Example 4-7, requires connecting to the device and
sending the search request. The second part, shown in in
Example 4-8, involves parsing and interpreting the search results.
Example 4-7. Step one of searching a device for a service with UUID 0xABCD
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
int main(int argc, char **argv)
{
uint8_t svc_uuid_int[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0xab, 0xcd };
uuid_t svc_uuid;
int err;
bdaddr_t target;
sdp_list_t *response_list = NULL, *search_list, *attrid_list;
sdp_session_t *session = 0;
str2ba( "01:23:45:67:89:AB", &target );
// connect to the SDP server running on the remote machine
session = sdp_connect( BDADDR_ANY, &target, SDP_RETRY_IF_BUSY );
// specify the UUID of the application we're searching for
sdp_uuid128_create( &svc_uuid, &svc_uuid_int );
search_list = sdp_list_append( NULL, &svc_uuid );
// specify that we want a list of all the matching applications' attributes
uint32_t range = 0x0000ffff;
attrid_list = sdp_list_append( NULL, &range );
// get a list of service records that have UUID 0xabcd
err = sdp_service_search_attr_req( session, search_list, \
SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
.
.
The uuid_t data type is used to represent the 128-bit UUID
that identifies the desired service. To obtain a valid
uuid_t, create an array of 16 8-bit integers and use the
sdp_uuid128_create function, which is similar to the
str2ba function for converting strings to
bdaddr_t types. sdp_connect
synchronously connects to the SDP server running on the target device.
sdp_service_search_attr_req searches the connected device
for the desired service and requests a list of attributes specified by
attrid_list. It's easiest to use the magic number
0x0000ffff to request a list of all the attributes
describing the service, although it is possible, for example, to request only
the name of a matching service and not its protocol information.
Continuing our example, we now get to the tricky part - parsing and
interpreting the results of a search. Unfortunately, there isn't an
easy way to do this. Taking the result of our search above,
Example 4-8 shows how to
extract the RFCOMM channel of a matching result.
Example 4-8. parsing and interpreting an SDP search result
sdp_list_t *r = response_list;
// go through each of the service records
for (; r; r = r->next ) {
sdp_record_t *rec = (sdp_record_t*) r->data;
sdp_list_t *proto_list;
// get a list of the protocol sequences
if( sdp_get_access_protos( rec, &proto_list ) == 0 ) {
sdp_list_t *p = proto_list;
// go through each protocol sequence
for( ; p ; p = p->next ) {
sdp_list_t *pds = (sdp_list_t*)p->data;
// go through each protocol list of the protocol sequence
for( ; pds ; pds = pds->next ) {
// check the protocol attributes
sdp_data_t *d = (sdp_data_t*)pds->data;
int proto = 0;
for( ; d; d = d->next ) {
switch( d->dtd ) {
case SDP_UUID16:
case SDP_UUID32:
case SDP_UUID128:
proto = sdp_uuid_to_proto( &d->val.uuid );
break;
case SDP_UINT8:
if( proto == RFCOMM_UUID ) {
printf("rfcomm channel: %d\n",d->val.int8);
}
break;
}
}
}
sdp_list_free( (sdp_list_t*)p->data, 0 );
}
sdp_list_free( proto_list, 0 );
}
printf("found service record 0x%x\n", rec->handle);
sdp_record_free( rec );
}
sdp_close(session);
}
Getting the protocol information requires digging deep into the search results.
Since it's possible for multiple application services to match a single search
request, a list of service records is used to describe each matching
service. For each service that's running, it's (theoretically,
but not usually done in practice) possible to have different ways of
connecting to the service. So each service record has a list of
protocol sequences that each describe a different way to
connect. Furthermore, since
protocols can be built on top of other protocols (e.g. RFCOMM uses L2CAP as a
transport), each protocol sequence has a list of protocols that the
application uses, only one of which actually matters. Finally, each
protocol entry will have a list of attributes, like the protocol type
and the port number it's running on. Thus, obtaining the port number for an
application that uses RFCOMM requires finding the port number protocol
attribute in the RFCOMM protocol entry.
In this example, several new data structures have been introduced that we
haven't seen before.
typedef struct _sdp_list_t {
struct _sdp_list_t *next;
void *data;
} sdp_list_t;
typedef void(*sdp_free_func_t)(void *)
sdp_list_t *sdp_list_append(sdp_list_t *list, void *d);
sdp_list_t *sdp_list_free(sdp_list_t *list, sdp_list_func_t f);
Since C does not have a built in linked-list data structure, and SDP search
criteria and search results are essentially nothing but lists of data, the
BlueZ developers wrote their own linked list data structure and called it
sdp_list_t. For now, it suffices to know that appending to a
NULL list creates a new linked list, and that a list must be
deallocated with sdp_list_free when it is no longer needed.
typedef struct {
uint32_t handle;
sdp_list_t *pattern;
sdp_list_t *attrlist;
} sdp_record_t;
The sdp_record_t data structure represents a single service record
being advertised by another device. Its inner details aren't important, as
there are a number of helper functions available to get
information in and out of it. In this example,
sdp_get_access_protos is used to extract a list of the protocols
for the service record.
typedef struct sdp_data_struct sdp_data_t;
struct sdp_data_struct {
uint8_t dtd;
uint16_t attrId;
union {
int8_t int8;
int16_t int16;
int32_t int32;
int64_t int64;
uint128_t int128;
uint8_t uint8;
uint16_t uint16;
uint32_t uint32;
uint64_t uint64;
uint128_t uint128;
uuid_t uuid;
char *str;
sdp_data_t *dataseq;
} val;
sdp_data_t *next;
int unitSize;
};
Finally, there is the sdp_data_t structure, which is ultimately
used to store each element of information in a service record. At a high
level, it is a node of a linked list that carries a piece of data (the
val field). As a variable type data structure, it can be used in
different ways, depending on the context. For now, it's sufficient to know
that each protocol stack in the list of protocol sequences is represented as a
singly linked list of sdp_data_t structures, and
extracting the protocol and port information requires iterating through this
list until the proper elements are found. The type of a
sdp_data_t is specified by the dtd field, which is
what we use to search the list.
Every Bluetooth device typically runs an SDP server that answers queries from
other Bluetooth devices. In BlueZ, the implementation of the SDP server is
called sdpd, and is usually started by the system boot scripts.
sdpd handles all incoming SDP search requests. Applications that
need to advertise a Bluetooth service must use inter-process communication
(IPC) methods to tell sdpd what to advertise. Currently, this is
done with the named pipe /var/run/sdp. BlueZ provides convenience
functions written to make this process a little easier.
Registering a service with sdpd involves describing the service to
advertise, connected to sdpd, instructing sdpd on what
to advertise, and then disconnecting.
Describing a service is essentially building the service record that was
parsed in the previous examples. This involves creating several lists and
populating them with data attributes. Example 4-9 shows
how to describe a service application with UUID 0xABCD that
runs on
RFCOMM channel 11, is named ``Roto-Rooter Data Router", provided by
``Roto-Rooter", and has the description ``An experimental plumbing router"
Example 4-9. Describing a service
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
sdp_session_t *register_service()
{
uint32_t service_uuid_int[] = { 0, 0, 0, 0xABCD };
uint8_t rfcomm_channel = 11;
const char *service_name = "Roto-Rooter Data Router";
const char *service_dsc = "An experimental plumbing router";
const char *service_prov = "Roto-Rooter";
uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid;
sdp_list_t *l2cap_list = 0,
*rfcomm_list = 0,
*root_list = 0,
*proto_list = 0,
*access_proto_list = 0;
sdp_data_t *channel = 0, *psm = 0;
sdp_record_t *record = sdp_record_alloc();
// set the general service ID
sdp_uuid128_create( &svc_uuid, &service_uuid_int );
sdp_set_service_id( record, svc_uuid );
// make the service record publicly browsable
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root_list = sdp_list_append(0, &root_uuid);
sdp_set_browse_groups( record, root_list );
// set l2cap information
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
l2cap_list = sdp_list_append( 0, &l2cap_uuid );
proto_list = sdp_list_append( 0, l2cap_list );
// set rfcomm information
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
rfcomm_list = sdp_list_append( 0, &rfcomm_uuid );
sdp_list_append( rfcomm_list, channel );
sdp_list_append( proto_list, rfcomm_list );
// attach protocol information to service record
access_proto_list = sdp_list_append( 0, proto_list );
sdp_set_access_protos( record, access_proto_list );
// set the name, provider, and description
sdp_set_info_attr(record, service_name, service_prov, service_dsc);
.
.
Building the description is quite straightforward, and consists of taking
those five fields and packing them into data structures. Most of the work is
just putting lists together. Once the service record is complete, the
application connects to the local SDP server and registers a new service,
taking care afterwards to free the data structures allocated earlier.
.
.
int err = 0;
sdp_session_t *session = 0;
// connect to the local SDP server, register the service record, and
// disconnect
session = sdp_connect( BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY );
err = sdp_record_register(session, record, 0);
// cleanup
sdp_data_free( channel );
sdp_list_free( l2cap_list, 0 );
sdp_list_free( rfcomm_list, 0 );
sdp_list_free( root_list, 0 );
sdp_list_free( access_proto_list, 0 );
return session;
}
The special argument BDADDR_LOCAL causes
sdp_connect to connect to the local SDP server (via the
named pipe /var/run/sdp) instead of a remote device. Once
an active session is established with the local SDP server,
sdp_record_register advertises a service record. The
service will be advertised for as long as the session with the SDP server is
kept open. As soon as the SDP server detects that the socket connection is
closed, it will stop advertising the service. sdp_close
terminates a session with the SDP server.
sdp_session_t *sdp_connect( const bdaddr_t *src, const bdaddr_t *dst, uint32_t
flags );
int sdp_close( sdp_session_t *session );
int sdp_record_register(sdp_session_t *sess, sdp_record_t *rec, uint8_t flags);