A Bluetooth piconet is quite different from a traditional LAN. Rather than connecting to a network, you connect straight to another device. Connections can change quickly, and in this dynamic environment, the structuring normally provided by a network manager is missing.
In a normal LAN, you find a connection to a printer, and once found, it stays in place for months, or years. Bluetooth is designed to allow you to walk into an area and find a printer without having to preconfigure settings. When you’ve used the printer, you can walk away and forget its details. Service Discovery Protocol (SDP) is the part of Bluetooth that allows this to happen. SDP provides the means to find a device that will print for you or to browse through the range of other services Bluetooth devices in the area can offer you.
Figure 11–1 shows SDP’s position in the Bluetooth protocol stack. SDP relies on L2CAP links being established between SDP client and server. Once an L2CAP link has been established, it can be used to find out about services and how to connect to them. L2CAP does not handle connections to services itself, it merely provides information. So, to find and connect to a service offered by an SDP server, a client must go through the following steps:
The L2CAP channel used for SDP can be dropped once it is no longer needed, or it may be left open if the client is likely to want to query the server’s database for more SDP services.
The L2CAP Protocol and Service Multiplexer (PSM) value for SDP is defined as 0x0001. Having a known PSM means that once an ACL link has been established, the SDP client automatically knows the number to use to establish a link with the SDP server on the remote device.
Bluetooth does not define a man machine interface for service discovery; it merely defines the protocol to exchange data between a server offering services and a client wishing to use them.
An SDP server is any Bluetooth device which offers a service or services to other Blue-tooth devices (a service is a feature usable by another device). Information about services is maintained in SDP databases. Each SDP server maintains its own database; there is no centralized database.
SDP clients use services provided by servers. To allow them to do this, servers and clients exchange information about services using service records.
The SDP database is simply a set of records describing all the services which a Bluetooth device can offer to another Bluetooth device. SDP provides the means for another device to look at these records.
An SDP service is described by service attributes, which provide the information a client needs to use the service.
Each attribute has a 16 bit identifier and a value, as shown in Figure 11–2. Attribute values can be text strings, Boolean values, or integers.
Attributes are used to pass information on services and on the hierarchy of services available. Each attribute in the record describes a different aspect of a service. Version 1.0b of the Bluetooth standard defines twenty-eight types of attributes:
Some of the attributes are specific to Bluetooth profiles. It is likely that as more Bluetooth profiles are defined, more types of attributes will be defined for them.
Attributes have values, and those values can have various types and sizes. So that a device receiving an attribute knows what type and size it is receiving, attributes are sent in data elements which begin with data element descriptors that describe the type and size of the attribute being sent.
The first byte of a data element contains the data element type descriptors; the first five bits are the data element type descriptor, and the next three bits are the data element size descriptor.
The data element type descriptor gives the type of attribute in the data element. There are nine different types numbered as follows:
The data element size descriptor gives the size of the attribute in the data element. It starts off with a size index. If the size index is 0, 1, 2, 3, or 4, it gives the length of the attribute which follows:
If the size index is 5, 6, or 7, it is followed by the size:
Why have such a complex system where sometimes there is a size field and sometimes there isn’t? It would have been possible to forget about having to always send the size in a 32 byte field, but many attributes will fit into 1, 2, 4, 8, or 16 bytes. Thus, for these attributes, it saves a significant amount of bandwidth to just use the size index and not send a separate size field. The data element type descriptor leaves three spare bits in its byte. These three bits are just enough to use for the size index, so this index isn’t using any extra bytes in the PDU. On the other hand, it’s not possible to avoid sometimes sending the size information separately because attributes which are made up of sequences and attributes which are text strings can be very long, so their length can’t be adequately described by the size indexes.
Examples of data elements are the easiest way to understand how the data element descriptor structures work. Figure 11–3 has an example of a data element containing a 16 bit integer. The first five bits have the type; this is 1, which indicates that the attribute is an unsigned integer. The size index is 1, which says the attribute is two bytes long. The attribute’s data then follows: first the least significant byte, then the most significant byte.
Figure 11–3 Data element containing a 16 bit integer.
Figure 11–4 has an example of a data element containing the string “Cats”. The first five bits have the type; this is 4, which indicates that the attribute is a text string. The size index is 5, which says the attribute size is in the next byte. The attribute’s size in bytes is then given. Since characters are encoded one byte per character, this size is also the length of the string. The attribute’s data, “Cats,” then follows.
Sometimes there is no attribute data to put in a parameter, but the parameter still has to be sent. In these cases, a null data element is used. A null data element is shown in Figure 11–5; it’s type is 0, indicating a null. The size index is also 0. Normally, this would mean one byte of data, but in combination with the null type, it means that there is no attribute data following.
Sometimes a parameter needs to carry several attributes. To do this, a data element sequence or data element alternative is used. Both of these allow a list of attributes to be sent. In a data element sequence, all of the attributes are to be used; in a data element alternative, only one attribute is chosen to be used.
Figure 11–6 shows an example data element sequence. This sequence contains a list of UUIDs—such a list might be sent as a ServiceSearchRequest parameter by a client trying to find out whether a server supported one of a set of services.
The first two bytes describe the sequence: the type is 6 for data element sequence. Because the sequence takes up six bytes, the size doesn’t fit in the size index, so an index of 5 is used to indicate that the size comes in the next byte. The size byte gives the length in bytes of the whole sequence.
Each data element in the data element sequence has its own data element descriptors. Both elements are UUIDs, so they both have a data element type descriptor of 3. Both UUIDs are two bytes long, so both data elements have a SizeIndex of 1.
There is no particular significance to the way the bytes are split up in the diagram; they have just been split up into two byte rows for convenience. However, the splitting up of the Dial Up Networking UUID illustrates the byte order that SDP uses with the most significant byte being sent first and least significant byte sent last.
A data sequence alternative is sent in a similar way, with one set of data element descriptors for the whole sequence, and then a data element descriptor for each data element in the sequence.
Attributes are values describing a service which a server is making available to clients. A service record holds all the information a server provides to describe a service, so it is made up of a series of attributes. The class of the service defines the meanings of the attributes, so an attribute might mean something different in different service records.
Table 11–1 shows an example of the Bluetooth headset’s service record, which illustrates how a service record is made up. The first part of the service record deals with the services offered by the device. The headset service is made up of two services: the headset service and the generic audio service on which the headset service is based. The service record then goes on to list the protocols that are needed to use the headset service. The headset service requires L2CAP and RFCOMM. The channel number of the RFCOMM server is needed before a device can access RFCOMM, so along with the protocol descriptor for RFCOMM, there is an extra protocol specific parameter. The client device knows how to use RFCOMM, so it knows that this protocol specific parameter is the channel number; retrieving this number allows the client to begin using RFCOMM.
Next comes a BluetoothProfileDescriptorList, which lists the profiles which the service supports. Profiles should only be listed if the device fully supports them, so it is possible for a device to have no list of profiles in any of its service records (for example, a Bluetooth development system may not support any profiles). Naturally, the headset service implements the headset profile, so the unique identifier for this profile is given, followed by a version number. The headset profile is based upon the serial port profile, and all profiles require the generic access profile. But as support of these two profiles is implicit in support of the headset profile, there is no need to explicitly list them. In fact, because the generic access profile is required for all other profiles, it has not even been assigned its own service class UUID in Bluetooth’s assigned numbers, so it is not possible to list it in an SDP service record.
The record finishes with some extra attributes needed to use the headset profile: the name given by the service provider to the headset service, which defaults to “Headset”, but could be changed by the service provider; and finally, an entry to inform the client that this headset does not support the optional feature allowing remote setting of headset volume by an audio gateway.
Note that one ServiceRecord may contain the ServiceName in several languages. The client adds different offsets to the AttributeID to get the attribute in different languages. The device’s primary language is given the offset 0x0100, so to get the Service-Name in the primary language, the client would add 0x0100 to the base ServiceName attribute ID of 0x0000 to give 0x0100.
To find out what the language offsets are for a device, the client would request the LanguageBaseAttributeIDList, which has attribute ID = 0x0006.
To make it easier to find the service you want, services are arranged in a hierarchy structured as a tree which can be browsed. Clients begin by examining the root of the hierarchy, then follow the hierarchy out to the leaf nodes, where individual services are described.
It is up to each service provider to decide what services will be browsable and how the browsing hierarchy will be constructed. For example, a smart phone offering several different services to other Bluetooth devices could have the browse structure shown in Figure 11–7.
The service provider has chosen to organize the services into two groups. The audio group covers audio gateway services, which allow Bluetooth headsets to connect to cellular connections and local Bluetooth intercom connections. The organizer group includes calendar facilities, which include the ability to synchronise calendars and alarms.
An alarm facility is common to many mobile phones, but is not defined under Blue-tooth, so the alarms category shows how SDP can be used to publish information about new services not defined in the Bluetooth specification. A user of the alarms service would have to know something about how it worked so that they could connect to it, but any parameters necessary to connect to this particular instance of the alarms service could be retrieved via SDP. For instance, the alarms service might provide the ability to set alarms via an RFCOMM connection, so the RFCOMM channel for connecting to the alarms service could be retrieved by querying the SDP service records for the alarms service.
Service classes are used to identify services. Each service class record includes a BrowseGroupList attribute. The value of the BrowseGroupList is a list of the UUIDs of all the browse groups associated with the service.
Table 11–2 shows the set of service records used for browsing the hierarchy in Figure 11–7. Each group entry lists the IDs of any groups above it and its own group ID. The services don’t just list the IDs of the groups they belong to.
The top level of the browsing tree is called PublicBrowseRoot and has the UUID 0x1002 (this is the short form, the long form of this is 0x00001002-0000-1000-7007-00805F9B34FB). All browsing hierarchies have this top level. Because there is no entry above the PublicBrowseRoot, and all devices know its Group ID, there is no need to include an entry for the PublicBrowseRoot in the table.
Universally Unique Identifiers (UUIDs) are 128 bit numbers. Each record has a UUID attribute; these are used to search for a record. The Bluetooth standard specifies UUIDs for the attributes needed for the Bluetooth profiles.
The Bluetooth specification’s UUIDs are likely to be used a lot, so to save bandwidth transferring them and space storing them, they can be shortened to 32 bit or 16 bit forms. This is possible because all the Bluetooth specification’s UUIDs are based in the same Bluetooth base UUID: 0x00000000-0000-1000-7007-00805F9B34FB.
To compare a pair of UUIDs, they must be converted into the same format. The rules for converting UUIDs assigned by the Bluetooth specification are quite simple:
0x1265*296 + 0x00000000-0000-1000-7007-00805F9B34FB = 0x00001265-0000-1000-7007-00805F9B34FB
A manufacturer defining a new service is allowed to allocate its own UUIDs. This could easily lead to clashes of IDs, so SDP uses a method of allocating UUIDs which makes them extremely unlikely to be duplicated.
A UUID is made up of the following parts:
- least significant 12 bits are the most significant 12 bits of the timestamp (bits 48-59).
- most significant 4 bits are the version.
- 6 least significant bits are the 6 most significant bits of the clock sequence (bits 8 to 13).
- 2 most significant bits are the variant (this may be set to 10).
The unique node identifier is used to guarantee that the UUIDs allocated by one node are different from those allocated by other nodes. A 48 bit IEEE address is used for this field. As these are allocated uniquely, it provides a convenient guarantee that the node ID is unique. Usually, the address used will be the address of the host.
The timestamp is 60 bits of coordinated Universal Time Clock (UTC), which is the number of 100 nanosecond intervals since 00:00:00.00 on 15 October 1582. This may seem like an odd date to pick, but it is the date of the Gregorian reform of the Christian calendar, so it is the start of the most widely used system of dating in use today. This timestamp will not wrap around to zero until around 3400 A.D.
The clock sequence value is used because some systems will not have a real-time clock that is calibrated to a date, or for some other reason their value of the coordinated UTC goes backwards. This could lead to duplication of UUIDs. To get around this problem, the 14 bit clock sequence value is changed every time the UTC value goes backwards and every time the system reboots. Random numbers can be used to make sure the clock sequence value changes if the system can’t store values across a reboot. The clock sequence value ensures that even if the same UTC value is generated twice, the UUIDs will still be different.
The variant field describes the layout of the UUID. Microsoft has reserved a variant ID of 110. Variants beginning with zero are reserved for backwards-compatibility with the Network Computing System (NCS), which originated the UUID system. The variant with MSBs 10 was used in an Internet draft and is used by the DCE 1.1 Remote Procedure Call specification. The binary value 10 is used in the Bluetooth base UUID, and it may be used in the variant field to assign UUIDs for Bluetooth SDP purposes.
The version field describes how a UUID was allocated. The time-based version described here is identified by the binary sequence 00011. There are other ways of assigning UUIDs, based on functions of names or on random numbers. Each method has its own different variant value, so they each produce a different range of UUIDs. This helps to ensure that UUIDs stay unique, whatever method was used to allocate them.
The parts of the UUID are written as a series of hexadecimal digits separated by hyphens in the following order:
UUID = time_low - time_mid - time_high_and_version - clock_seq_hi_and_reserved - clock_seq_low - node
For storage and transferring in a data stream, the UUID is just treated as a sequence of octets.
The full rules on using and assigning UUIDs are available at: http://www.opengroup.org/publications/catalog/c706.htm or http://www.iso.ch/cate/d2229.html, and in the International Organization for Standardization publication ISO/IEC 11578:1996, “Information Technology—Open Systems Interconnection-Remote Procedure Call (RPC)”.
To browse service classes or get information about a specific service, SDP clients and servers exchange messages. These messages are carried in SDP Protocol Data Units (PDUs). There are only seven types of SDP PDUs defined, and each has its own PDU ID to identify it:
The client always initiates SDP transactions with a request; the server always answers with a response as shown in Figure 11–8. (Note that some responses may be split across several packets.)
All SDP PDUs share a common structure as explained in section 11.5.1.
SDP uses Protocol Data Units (PDUs) with the structure shown in Figure 11–9. The first byte is an ID, identifying the message in the PDU. This is followed by two bytes of transaction ID. When a client sends an SDP request, it is given a transaction ID; the server copies that transaction ID into the response, so that if the client sends several requests, it can sort out which response goes with which request.
Except for the SDP_ErrorResponse, all SDP PDUs have a ContinuationState as their last parameter. This allows a message to be split across more than one PDU. If the message is not continued, the ContinuationState is a single byte set to zero. If the message is continued, then the first byte of the ContinuationState parameter begins with a single byte length field followed by some ContinuationInformation. The ContinuationInformation is a handle which can be used to request the rest of the message: the client simply repeats the request and copies the ContinuationState parameter from the partial response into the last parameter of the request. When the server receives the request, it uses the ContinuationInformation to identify where it split the response and continues the next response where it left off.
Figure 11–10 shows an SDP PDU with a ContinuationState parameter showing the parameter starting with a single byte of InfoLength, followed by the ContinuationInformation.
Whether a client is starting at the public browse root and searching through the hierarchy of services, or looking for a specific service, it uses an SDP_ServiceSearchRequest. This contains a ServiceSearchPattern made up of UUIDs, which the server is to look for in its database. The server responds with an SDP_ServiceSearchResponse containing information about any service records which match the ServiceSearchPattern as shown in Figure 11–11.
The ServiceSearchRequest has three parameters:
The structure of an example SDP_ServiceSearchRequest is shown in Figure 11–12. It begins with the header used by all SDP PDUs:
The header is followed by the message’s parameters: ServiceSearchPattern, MaximumServiceRecordCount, and ContinuationState. The ServiceSearchPattern is made up of several parts.
In this case, the data element sequence just contains one data element, which is made up as follows:
The single UUID completes the ServiceSearchPattern parameter. This is followed by the last parameter:
The PDU contains a single byte of ContinuationState.
The ContinuationState is a parameter like all the others, so the parameter length includes the lengths of the ServiceSearchPattern, the MaximumServiceRecordCount, and the ContinuationState.
Note that the ServiceSearchPattern parameter can contain up to twelve UUIDs. Because its length can vary, it begins with data element type and size descriptors, giving the length of the sequence of UUIDs. In this example, there is only one UUID, and since UUIDs can vary in length, it too must begin with data element type and size descriptors to let the server know its length. The other parameters have fixed types and lengths, so they don’t have to have data element type and size descriptors.
A server receiving a valid SDP_ServiceSearchRequest replies with an SDP_Service-SearchResponse. This has four parameters:
The structure of an example SDP_ServiceSearchResponse is shown in Figure 11–13. (There is no significance to the way the structure has been drawn with different numbers of bytes on different lines; the diagram has just been broken up into rows at convenient places.) The SDP_ServiceSearchResponse begins with the header used by all SDP PDUs:
The header is followed by the message’s parameters: TotalServiceRecordCount, CurrentServiceRecordCount, ServiceRecordHandleList, and ContinuationState:
The service handles returned by this message can be used to request the SDP server for the service’s attributes.
Once a client has a service’s handle, it can use an SDP_ServiceAttributeRequest to retrieve the service’s attributes. Figure 11–14 shows an example of such a request. It begins with the header used by all SDP PDUs:
The header is followed by the message’s parameters—ServiceRecordHandle, MaximumAttributeByteCount, AttributeIDList, and ContinuationState.
The SDP_ServiceAttributeRequest in section 11.5.4 asked for the ProtocolDescriptorList associated with a headset service. Table 11–1 shows that a headset service record has a ProtocolDescriptorList with two protocols: L2CAP and RFCOMM, and the RFCOMM protocol has one parameter associated with it: the server channel number used for the headset service.
Figure 11–15 shows how the headset’s ProtocolDescriptorList is fitted into a SDP_ServiceAttributeResponse. The PDU begins with the header used by all SDP PDUs:
The header is followed by the message’s parameters: AttributeListByteCount, AttributeList, and ContinuationState:
The AttributeList parameter is split up into several parts:
Sections 11.5.2 and 11.5.4 described how a client searches for a service and how a client requests the attributes of a service. Sections 11.5.3 and 11.5.5 described how the server responds to those requests. Often clients wanting to connect to a service will want to find out the services available and some of their attributes. To speed up the process, SDP offers a message which combines a service search with an attribute request.
Figure 11–16 shows an example of an SDP_ServiceSearchAttributeRequest. It begins with the header used by all SDP PDUs:
The header is followed by the message’s four parameters: ServiceSearchPattern, MaximumAttributeByteCount, AttributeIDList, and ContinuationState.
Figure 11–17 shows an example of how the headset service’s ServiceRecordHandle and ServiceClassIdList can be fitted into a pair of SDP_ServiceSearchAttributeResponses. The response has been split across two response packets. This may have to be done because the SDP PDU size may be larger than the L2CAP Maximum Transmission Unit (MTU), or memory limitations in an embedded device may make it easier for it to generate a series of small responses rather than one large response.
The structure is similar to the examples above, using the standard header followed by the parameters AttributeListByteCount, AttributeLists, and ContinuationState.
Note that the AttributeLists parameter is made up of a sequence of two data elements: the information retrieved for the first service and the information retrieved for the second service. (The information for the second service is incomplete because it is continued in another PDU.) This means that the AttributeLists parameter’s data element descriptor gives its type as a sequence of data elements.
The information on the first service is also a data element sequence, because it also has two data elements: the Service RecordHandle and the attribute list for the first service. The ServiceRecordHandle is a 32 bit unsigned integer, but the attribute list is again a sequence of data elements; in this case, the attributes requested for that service.
So, the PDU’s AttributeIDList consists of nested data elements with sequences inside sequences inside sequences until finally the attributes of services matching the search pattern are reached.
The example PDU given in Figure 11–17 has ContinuationState = 2, indicating that the PDU does not have a complete response and that there are two bytes of Continuation-Information. When the client receives the ServiceSearchAttributeResponse with a nonzero continuation state, it reads the ContinuationInformation, and to get the second part of the response, the client reissues the SDP_ServiceSearchAttributeRequest with the ContinuationState ContinuationInformation copied into the end of the PDU (see Figure 11–18).
The SDP server receives the second request with a non-zero ContinuationState and uses the ContinuationInformation to work out where it stopped sending data in the first PDU. It can then send the rest of the data in a continuation PDU. Since this is the last PDU in the sequence, it has a zero value for ContinuationState.
In the second PDU, in Figure 11–19, there is only an attribute list for one service. Since there is no list of attributes for several services, and no list of service handle followed by service attributes, there are not so many levels of nested sequence data elements. Note: for simplicity, our example response omits the IconURL, so does not match the example request.
If an SDP server receives a request which it can’t respond to correctly, it replies with an SDP_ErrorResponse. This carries two parameters: an ErrorCode and additional ErrorInfo.
In version 1.0b, the format of the ErrorInfo is not defined. It is difficult to know what to do with an undefined parameter. One possibility is to ignore it, but since some sort of ErrorInfo parameter is specified, some implementations might regard it as an error if one isn’t supplied, so perhaps a better solution is to send a null data element as illustrated in Figure 11–20.
There are six possible values specified for ErrorCode:
The example shown in Figure 11–20 has ErrorCode 3, which shows that it is a response to a request with invalid syntax.
The service discovery profile describes how applications running on an SDP client use SDP and other features of the Bluetooth protocol stack to discover services provided by Bluetooth devices within range.
The service discovery profile provides:
Figure 11–21 shows how the various layers of the stack must each connect in turn to set up an SDP session. The SDP application profile gives examples of when these connections might be set up; for instance, a client might already be connected when it begins an SDP session, or it might only connect in response to a user requesting information.
The primitives provided in the service discovery application profile include ServiceBrowse and ServiceSearch, which simply use SDP’s browsing and searching capabilities. There is also an enumerateRemDec primitive, which causes an inquiry and results in the application reporting the other Bluetooth devices in the neighborhood. Finally, there is a terminatePrimitive, which causes the link to a device to be torn down after the SDP application has finished with it.
The Service Discovery Protocol (SDP) provides a means for an SDP client to access information about services offered by SDP servers. A server can be any Bluetooth device offering a service which can be used by another Bluetooth device, and a client can be any device wanting to use a service. So, a device could be an SDP client and server at the same time.
SDP servers maintain a database of service records. Each service record provides information that a client needs to access a service. This information may include URLs for executables, documentation, and icons associated with the service. So, a client may have to follow these URLS and retrieve information from elsewhere to be able to use the service.
To use SDP, an L2CAP channel must be established between the SDP client and server. This channel has a protocol service multiplexer reserved for SDP, so that any device can easily connect to the SDP service on another device. After the SDP information has been retrieved from the server, the client must establish a separate connection to use the service (the connection used for SDP cannot be used for anything else).
Services have Universally Unique Identifiers (UUIDs) which describe them. The services defined by the Bluetooth profiles have UUIDs assigned by the standard, but service providers can define their own services and assign their own UUIDs to those services. The UUIDs are allocated by a method that guarantees they will not be duplicated, so there is no need for a central authority or a central database to allocate the UUIDs.
A UUID can be sent in a message asking a server if it supports the service identified by the UUID. Alternatively, instead of asking for a specific service, SDP can provide a mechanism for organising services in trees, along with messages for browsing through the trees to look for a service.
SDP does not define the applications needed to drive the service discovery process, nor does it define an interface to applications; this is left up to implementers. If required, it may be used alongside other service discovery methods which provide Application Programming Interfaces (APIs) such as salutation or SLP (Service Location Protocol).
18.188.200.164