Affix - Technical Documentation: Affix - Open Source Bluetooth Protocol Stack for Linux | ||
---|---|---|
Prev | Chapter 3. Application Programming Interface | Next |
Common feature for Bluetooth connections are that they are dynamic and thus also the services in the radio range varies. A special method is needed to discover the available services. The Service Discovery Protocol (SDP) provides means for that. It also defines how to find out the characteristics of the service.
Using SDP a client is able to discover services with specific attributes or just browse the available ones. SDP also defines unique identifiers for services, class of services and attributes. In addition to that dynamic attributes can be created for services and these need not be registered with some central authority.
In Affix the SDP API is divided into two pars: service client API and service provider API. The Service client API is dedicated for clients to find out what services are available on a remote device, what attributes they have and how to connect and properly use a service.
The Service provider API is dedicated for services. They can register information about themselves with SDP server. A SDP server has list of services that it can provide. All services have set of attributes. The attributes of a service include the type or class of service offered and the mechanism or protocol information needed to utilize the service. Each registered service has one service record at the server. This record contains all the service's attributes.
A client sends a SDP request to the server to get information on the characteristics of a service. This requires communication between the server and client. L2CAP protocol is used to send SDP request and answers.
One Bluetooth device can have only one SDP server, but it can function both as client and as a server simultaneously. If several applications offer services those have to register their services with the SDP server. Every service provider cannot act as a SDP server.
SDP provides only information on services. It cannot be used to establish a connection to some certain service. Although available services changes, SDP cannot be used to notify clients about the new services. But, if needed, clients can poll a SDP server to get this information.
An universally unique identifier (UUID) is guaranteed to be unique across all space and all time. UUIDs can be independently created in a distributed fashion. No central registry of assigned UUIDs is required. A UUID is a 128-bit value. UUIDs are used to uniquely identify service classes.
To reduce the burden of storing and transferring 128-bit UUID values, a range of UUID values has been preallocated for assignment to often-used, registered purposes. The first UUID in this preallocated range is known as the Bluetooth Base UUID and has the value 00000000-0000-1000-8000- 00805F9B34FB, from the Bluetooth Assigned Numbers document. UUID values in the preallocated range have aliases that are represented as 16-bit or 32-bit values. These aliases are often called 16-bit and 32-bit UUIDs, but it is important to note that each actually represents a 128-bit UUID value.
UUID object (see definition below) is used to store UUID, which can store 16, 32 or 128 bits UUID. To simplify interface to this object Affix provides set of functions to create UUID from a value and convert one type of UUID to another.
typedef struct { int type; union { uint16_t uuid16Bit; uint32_t uuid32Bit; uint128_t uuid128Bit; } value ; } uuid_t;
Table 3-9. UUID helper functions (defined in sdpclt.h)
Function | Purpose |
---|---|
void sdp_val2uuid16(uuid_t *UUID, uint16_t value16Bit); | Convert a 16 bit UUID (integer) to 128 bit value (struct uuiid_t) |
void sdp_val2uuid32(uuid_t *UUID, uint32_t value32Bit); | Convert a 32 bit UUID (integer) to 128 bit value (struct uuiid_t) |
void sdp_val2uuid128(uuid_t *UUID, uint128_t *value128Bit); | Convert a 128 bit UUID (integer) to 128 bit value (struct uuiid_t) |
int sdp_match_uuid(slist_t *searchPattern, slist_t *targetPattern); | Return 1 if each and every uuid_t in the search pattern exists in the target pattern. |
int sdp_uuidcmp(uuid_t *u1, uuid_t *u2); | uuid_t comparison function that returns 0 if values match. Otherwise if uuidValue1 < uidValue2 returns -1. If uuidValue1 > uidValue2 returns 1. |
int sdp_uuidcmp32(uuid_t *u1, uint32_t u2); | Same as above, but second argument is first converted to uuid_t. |
int sdp_uuid2val(uuid_t *uuid); | Returns the UUID as integer. Result is always 32 bit long. |
void sdp_print_uuid(uuid_t *uuid) | Prints the given UUID value. |
Types of services varies a lot. A service can provide information, perform an action (like printing) or control some third resource. Implementation might be done with hardware or software or combining these.
A SDP server maintains information about it's services in a list of service records. A service record contains a set of attributes that describes the characteristics of the service. Affix defines service record like follows:
typedef struct { uint32_t serviceRecordHandle; BD_ADDR *bda; /* service owner */ int state; sdppdu_t pdu; /* PDU form and the entire service record */ slist_t *targetPattern; /* all possible uuid_t */ slist_t *attributeList; int fd; /* to remove service */ } sdpsvc_t ;
Above structure is a composite of many attributes. Still not all of them need be present. References of those composites not present would be set to NULL. The service record handle identifies the service uniquely within the server. Note that identifiers in two separate servers do not match although the service might be the same.
There is one exception to that. A service record handle whose meaning is the same on all SDP servers. It's value is 0x00000000 and it represents the SDP server itself.
Table 3-10. Service record API
Function | Purpose |
---|---|
sdpsvc_t *sdp_create_svc(void); | Create an empty service record. |
void sdp_free_svc(sdpsvc_t *svcRec); | Remove the given service record. |
void sdp_free_svclist(slist_t **svcList); | Remove the given list of service records. |
void sdp_gen_svc_pdu(sdpsvc_t *svcRec) | Serialize the given service record to sdppdu_t structure. |
void sdp_print_svc(slist_t *svcAttrList) | Print all the attributes in the given list. |
void sdp_add_uuid_to_pattern(sdpsvc_t *svcRec, uuid_t *uuid) | Add an UUID to service records attribute list. It is a sorted list where included attributes are found quickly. |
Each service attribute describes a single characteristic of a service. Universal attributes are defined in the SDP specification. Below is listed some of the universal attributes. In addition to those service providers can define their own attributes. An attribute consist of two components: service attribute ID and the value.
The attribute ID is 16 bit unsigned integer which distinguishes all the attributes within one service record. It also defines the type of the attributes value (see the following chapter). A service class defines a set of attributes that are common to that classes instances. All services belonging to a given service class assign the same meaning to each particular attribute ID.
The following table lists the most common universal attributes. Table has the definitions and a brief description of the purpose. You can find the rest of the definitions in file sdp.h.
Table 3-11. Universal service attributes
Attribute definition | Purpose |
---|---|
SDP_ATTR_SERVICE_RECORD_HANDLE | An unique service identifier (32 bits) valid only on the device that gave out the handle. The same service on two different devices might probably have different handles. |
SDP_ATTR_SERVICEID | An universally unique service identifier (UUID) guaranteed to be the same on all devices where the service can be found. |
SDP_ATTR_SERVICE_CLASSID_LIST | A list of UUIDs representing the hierarchy (type) of service classes a service is known to conform to. Usually specified in the order "most specific" to "most generic". Since the format and meanings of many attributes in a service record are dependent on the service class of the service, the ServiceClassIDList attribute is very important. Its value should be examined or verified before any class-specific attributes are used. |
SDP_ATTR_PROTO_DESC_LIST | Protocol description is the information on what or which protocols can be used to gain access to this service. It is a sequence of attributes - UUID of protocol, version number and port number. |
SDP_ATTR_PROFILE_DESC_LIST | List of standard "Bluetooth Profile" descriptions that the service is known to conform to. Bluetooth profile description contains UUID of profile, its version number. |
SDP_ATTR_SERVICE_RECORD_STATE | A 32-bit integer provided to facilitate caching of service attributes. If this attribute is present in a service record, then it is guaranteed to change on any modification to the record. |
SDP_ATTR_SERVICE_INFO_TTL | A 32-bit integer that contains the number of seconds for which the service record is expected not to change, but not a guarantee. The time interval is measured from the time the attribute is received from the SDP server. Client can use this value when judging polling frequency. |
SDP_ATTR_SERVICE_AVAILABILITY | An 8 bit unsigned integer that represents the measure of service's capability to handle more clients. 0x00 means completely unavailable and 0xff means maximum availability. |
SDP_ATTR_BROWSE_GROUP_LIST | List of UUIDs representing the "browse group" the service belongs. Browse group is a hierarchy of service categories facilitating service discovery without "a priori" information of any service. |
SDP_ATTR_EXEC_URL | Location of a client platform specific (Win, Unix, Palm) application that can use the service. The operating system type is contained in the body of the URL. The first byte with the value 0x2A (ASCII character * ) is to be replaced by the client application with a string representing the desired operating environment. |
SDP_ATTR_DOC_URL | Location of additional service documentation. |
SDP_ATTR_ICON_URL | Location of an icon that can be used to represent the service. |
SDP_ATTR_SERVICE_DB_STATE | An attribute specific to the service discovery server itself. If this value changes, then the service repository was recently modified. |
SDP_ATTR_LANG_BASE_ATTRID_LIST | Language specific attribute identifiers for user visible strings. A base attribute ID is assigned to each of the natural languages used in the service record. When retrieving information in a specific language the attribute ID is composed of two elements: language's base attribute ID (read from this list) and information attribute ID offset (see below). |
SDP_ATTR_SERVICE_NAME, SDP_ATTR_SERVICE_DESC, SDP_ATTR_PROVIDER_NAME | User visible strings conveying information about a service. These can be provided in one (universal) or more languages. These defines are rather attribute ID offsets. Use these together with base attribute ID. To get a string in specific language first get the languages base attribute ID from SDP_ATTR_LANG_BASE_ATTRID_LIST attribute and then add the attribute offset to it. Information provided is service's name, brief description of the service and the provider name. |
In addition to universal attributes, a service could have "service specific attributes". These attributes make sense only in the context of the said service. Dynamic attributes could be either basic attributes or constructed.
The attribute value is a variable length field whose meaning is determined by the attribute ID associated with it and by the service class of the service record in which the attribute is contained. SDP uses a simple mechanism to describe the data contained within an attribute value.
A data element is a typed data representation. It has header and data fields. Header field in turn has also two fields: data type and size. This is called Data Type Descriptor (DTD). A data element type is represented as a 5-bit type descriptor. The type descriptor is contained in the most significant (high-order) 5 bits of the first byte of the data element header. Data types are defined in sdp.h and are in form SDP_DTD_<type>. Where <type> is the data type. For example:
#define SDP_DTD_UINT32 0x0A /* Defines an unsigned 32 bit integer*/ #define SDP_DTD_STR16 0x26 /* Defines a string which length is stored in a 16 bit integer */
SDP defines two special data types: sequence and alternative. Data element sequence, a data element whose data field is a sequence of data elements. Alternative is a data element whose data field is a sequence of data elements from which one data element is to be selected.
The data element size descriptor is represented as a 3-bit size index followed by 0, 8, 16, or 32 bits. The size index is contained in the least significant (low order) 3 bits of the first byte of the data element header.
The generic data structure sdpdata is meant to hold any attribute, universal or dynamic. Each sdpdata structure contains one attribute. The sdpdata object contains the service attribute identifier, a Data Type Descriptor followed by the value. Protocol Data Unit (PDU) is a serialized presentation of the object.
typedef struct sdpdata { 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 *stringPtr; slist_t *dataSeq; } value; sdppdu_t pdu; int unitSize; } sdpdata_t;
Affix has set of functions that can be used to create and manipulate attributes.
Table 3-12. Service Attribute API
Function | Purpose |
---|---|
sdpdata_t *sdp_create_data(uint8_t dtd, void *pValue); | Creates a SDP attribute object of certain type. |
void sdp_free_data(sdpdata_t *pData); | Destroys entire attribute object. |
int sdp_attrcmp(const void *key1, const void *key2) | Compares if given two attributes are the same. Returns zero if they are the same. |
void sdp_print_attr(void * value, void * userData) | Print attribute type and value. XXX userData is not used. Why this parameter is still here? |
sdpdata_t *sdp_put_<type>(<type> val); | Family of functions to create a new attribute structures and puts the given value of type <type> in the new object. These functions are defined in sdpclt.h. Return a pointer to the new structure. |
sdpdata_t *sdp_append<type>(sdpdata_t *seq, <type> val); | Family of functions to use with sequence type data. This adds a value of type <type> to the given sequence. These functions are defined in sdpclt.h. Return a pointer to the new structure. |
<type> sdp_get_<type>(sdpdata_t *data); | Family of functions to get data value of <type> from an attribute. |
sdpdata_t *sdp_create_seq(void) | Create new attribute with one value of type sequence. |
sdpdata_t *sdp_append_seq(sdpdata_t *seq) | Append a new sequence type data element to attribute. |
slist_t *sdp_get_seq(sdpdata_t *data); | Get the sequence type data in a linked list. Each list node presents one data item in the sequence. |
sdpdata_t *sdp_create_alt(void) | Create new attribute with one value of type alternative. |
sdpdata_t *sdp_append_alt(sdpdata_t *seq) | Append a new alternative type data element to attribute. |
Service attributes are stored in service records. Affix has functions to add, remove and get attributes from a service record. These functions are the glue between these two structures.
Table 3-13. Functions to glue service attributes and records
Function | Purpose |
---|---|
sdpdata_t *sdp_append_attr(sdpsvc_t *svcRec, uint16_t attrId, sdpdata_t *data) | Adds attribute object to the attribute list of ServiceRecord object. |
void sdp_remove_attr(sdpsvc_t *svcRec, uint16_t attrId) | Removes an attribute object from a service record. |
sdpdata_t *sdp_get_attr(sdpsvc_t *svcRec, uint16_t attrId); | Get a pointer to the value of an attribute in a service record. |
char *sdp_get_string_attr(sdpsvc_t *svcRec, uint16_t attrID) | Get the string value of given attribute. Note! Attribute have to be string type. No type conversion is done. |
The client SDP API is used by a client to find services provided by a remote device and to find out what attributes they have. The overall function set is shown on the following table:
Table 3-14. Service client SDP API
Function | Purpose |
---|---|
int sdp_init(int flags) | Initializes the SDP infrastructure. Flag is whether SDP_SERVER or SDP_CLIENT. |
void sdp_cleanup(void) | Removes all data structures needed by SDP. |
int sdp_connect(struct sockaddr_affix *saddr) | Opens a connection to the SDP server. |
void sdp_close(int srvHandle) | Closes a connection to the SDP server. |
int sdp_search_req( int srvHandle, slist_t *svcSearchList, uint16_t maxSvcRecordCount, slist_t **svcResponseList, uint16_t *handleCountInResponse) | Make a service search request. Returns E_OK on success and E_FAILURE on an error. Parameter svcSearchlist is a singly linked list containing elements of the search pattern. Each entry in the list is a uuid_t (DataType SDP_DTD_UUID16) of the service to be searched. maxSvcRecordCount sets the maximum number of entries to return. After returning svcResponseList points to the list of found services and handleCountInResponse has the number of services found. |
int sdp_attr_req(int srvHandle, uint32_t svcHandle, sdp_attrreq_t attrReqType, slist_t *attrIDList, uint16_t maxAttrIDByteCount, sdpsvc_t **_svcRec, uint16_t *maxAttrResponseByteCount) | Make a request for a service's attributes. Parameter svcHandle has the handle for the requested service on a server which handle is srvHandle. attrReqType specifies in which form the attribute IDs are given and attrIDList has the actual list of attributes to request. maxAttrIDByteCount contains the maximum length of the response. Return E_OK on success and maxAttrResponseByteCount points to an integer that has the number of bytes of attributes returned. |
int sdp_search_attr_req(int srvHandle, slist_t *svcSearchList, sdp_attrreq_t attrReqType, slist_t *attrIDList, uint16_t maxAttrByteCount, slist_t **svcResponseList, uint16_t *maxAttrResponseByteCount) | A service search request combined with the service attribute request. First a service class match is done and for matching service, requested attributes are extracted. Parameters and return values are as in the two functions above. |
int sdp_get_<attr_name>_attr(sdpsvc_t *svcRec, <attr_type>) | Template of functions: extracts individual attributes from service record, e.g.: service name, access port, profile id. <attr_name> in the function name body specifies the received attribute. Second parameter is a pointer of the type of the attribute. It will point to the attributes value after return. See function names for different attributes in sdpclt.h. |
First of all the SDP infrastructure must be initialized using sdp_init(SDP_CLIENT) call.
Before any request to the SDP server can be made, a client has to make a connection to the SDP server using following function: int sdp_connect(struct sockaddr_affix *saddr). This function accepts one parameter - address of the server and returns SDP server handle, that is needed later with several functions. An Address is given using Affix sockaddr structure.
To close a connection, call void sdp_close(int srvHandle) and give the handle got when connecting as a parameter.
Service search on the basis of given search pattern consisting of a set of service class identifiers. The maximum number of service class identifiers in the pattern is 12. Use function sdp_search_req() making the request.
The search pattern (svcSearchList) is a list of UUIDs (the UUID objects can either be 16, 32 or 128 bits). The UUID objects can be created using the helper functions described in chapter Section 3.4.1.
The client can limit the number of service record handles it expects from the server, and this is specified in maxSvcRecordCount. If there is no limit, set this to a large value.
The server response to this request is a list of matching service record handles (if any), and this is set up in svcResponseList (a slist_t) and returned to the caller. The number of handles found is set in handleCountInResponse.
Search for a specified set of service attributes in a specific service. This request usually follows a previous service class identifier based search. Once a service has been identified, this request is used to find more about the service. Brief descriptions of the arguments follow:
Service record handle of the service need to be provided to uniquely identify the service which attributes we would like to request.
The API supports requesting the entire range of attributes or a certain subset of them. This is specified in the request type (parameter attrReqType), followed by the actual list of attributes in attrIdList (type slist_t). Attribute identifiers are 16 bit unsigned integers specified in one of 2 ways. If type is IndividualAttributes - 16bit individual identifiers actual attribute identifiers in ascending order. If type is RangeOfAttributes - 32bit identifier range; the high-order 16bits is the start of range, the low-order 16bits are the end of range; 0x0000 to 0xFFFF gets all attributes.
maxAttrIDByteCount is the maximum byte count that the client expects to receive. The server will never return a response containing attribute byte count greater than this value.
maxAttrResponseByteCount - this is a pointer to a 16-bit integer, which is set to indicate the number of bytes of attributes returned.
_svcRec - this is a pointer to pointer to service record that contains the requested attributes. There are lot's of handy functions to extract information from the service record. See the earlier chapters.
A successful execution of the command results in E_OK being returned else a negative value indicating the type of error (timeout, invalid arguments etc) is returned.
This request combines the service class identifier and the service attribute search request, thus minimizing the number of request/response pairs needed to find a service. Use function sdp_search_attr_req() to make this request. The parameters contain a service search pattern and a list of attributes that need to be fetched, should a service record match the service search pattern.
First a service class match is done and for matching service, requested attributes are extracted. See the previous two chapters for explanation on the parameters
As before the method returns E_OK on successful execution of request and an error code for failures.
Bluetooth service developers can use the following API to register services with the "local" SDP server. The "on-the-wire" formats of service registrations mimic the SDP. However a key difference is that service registrations Protocol data units (PDU) are never sent on Bluetooth channels, but done using TCP sockets. The service registration requests and responses use the "reserved range" of SDP PDU Identifiers. There is no problem since it is not sent on BT channels. The following are the APIs available for service implementers to register their service with the SDP server on the device.
Table 3-15. Service provider SDP API
Function | Purpose |
---|---|
int sdp_init(int flags) | Initializes the SDP infrastructure. Flag is whether SDP_SERVER or SDP_CLIENT. |
int sdp_connect_local(void) | Connect to a local SDP server and start it if it is not running. Returns a server handle that is needed with other functions. |
void sdp_close(int srvHandle) | Closes a connection to the SDP server. |
sdpsvc_t *sdp_create_rfcomm_svc(uint16_t svc_class, uint16_t generic_class, uint16_t profile, char *name, char *prov, char *desc, int port) | Creates a Service Record object and set common RFCOMM attributes. |
sdpsvc_t *sdp_create_pan_svc(uint16_t svcClass, slist_t *ptype, uint16_t security, uint16_t type, uint32_t rate) | Creates a Service Record object and set common PAN attributes. |
int sdp_register_service(int srvHandle, sdpsvc_t *svcRec) | Registers a Service Record object with the SDP server. |
int sdp_update_service(int srvHandle, sdpsvc_t *svcRec) | Updates a Service Record object on the SDP server. |
int sdp_delete_service(int srvHandle, sdpsvc_t *svcRec) | Deletes a Service Record object a service record pointed to by svcRec from SDP server. |
int sdp_delete_service_handle(int srvHandle, uint32_t svcRecHandle); | Delete a service record which service handle is svcRecHandle. |
Before registering or updating a service record at the server you have to fill out the service record structure. Several helper functions exists to help your work. Couple of general functions were covered in chapter Section 3.4.2. Still most of the functions are defined in sdpsrv.h and will be discussed here.
Note also that if you are coding a service provider only manipulating service record is not enough. You have to call sdp_update_service() or sdp_register_service() function the changes to have an effect on the server. Further note that the pointers passed to helper functions must not be deleted for the life-time of the service as these are stored. Copies are not made of attributes pointed to by these pointers.
The following table presents the functions you can use to manipulate service records.
Table 3-16. Service record manipulation functions
Function | Purpose |
---|---|
int sdp_set_info_attr(sdpsvc_t *svcRec, char *serviceName, char *providerName, char *serviceDescription) | Set the service name, description and provider attributes. |
sdpdata_t *sdp_set_class_attr(sdpsvc_t *svc) | Add a service class attribute to a service record and return a pointer to it. |
sdpdata_t *sdp_set_proto_attr(sdpsvc_t *svc) | Add a protocol list attribute to a service record and return a pointer to it. |
sdpdata_t *sdp_set_subgroup_attr(sdpsvc_t *svc) | Add a browse group attribute to a service record and return a pointer to it. |
sdpdata_t *sdp_set_lang_attr(sdpsvc_t *svcRec) | Add a language base attribute list to a service record and return a pointer to it. |
sdpdata_t *sdp_set_state_attr(sdpsvc_t *svcRec, uint32_t svcRecState) | Add service record state attribute and set its value to svcRecState. Return a pointer to modified record. |
sdpdata_t *sdp_set_service_attr(sdpsvc_t *svcRec, uint16_t svcUUID) | Add service ID attribute and set its value to svcUUID. Return a pointer to modified record. |
sdpdata_t *sdp_set_group_attr(sdpsvc_t *svcRec, uint16_t groupUUID) | Add group ID attribute and set its value to groupUUID. Return a pointer to modified record. |
sdpdata_t *sdp_set_availability_attr(sdpsvc_t *svcRec, uint8_t svcAvail) | Add availability attribute and set its value to svcAvail. Return a pointer to modified record. |
sdpdata_t *sdp_set_profile_attr(sdpsvc_t *svc) | Add a profile description list attribute to a service record and return a pointer to it. |
int sdp_set_url_attr(sdpsvc_t *svcRec, char *clientExecURL, char *docURL, char *iconURL) | Set executable, icon and documentation URL attributes. Returns zero on success and -1 on an error. |
SDP server API provides also some functions to manage service attributes. See the following table.
Table 3-17. SDP server API for attribute manipulation
Function | Purpose |
---|---|
sdpdata_t *sdp_add_class(sdpdata_t *attr, uint16_t uuid) | Add class ID value to service attribute structure. |
sdpdata_t *sdp_add_subgroup(sdpdata_t *attr, uint16_t uuid) | Add group ID value to service attribute structure. |
sdpdata_t *sdp_add_proto_list(sdpdata_t *attr) | Add protocol description list to attribute structure. |
sdpdata_t *sdp_add_proto(sdpdata_t *attr, uint16_t uuid, uint16_t portNumber, int portSize, uint16_t version) | Add a protocol description to attribute structure. |
int sdp_add_lang(sdpdata_t *attr, uint16_t lang, uint16_t encoding, uint16_t offset) | Add a language base information to attribute structure. |
In the following example code first a service class attribute is added to the service record. Then the value is set to SDP_UUID_SDP_SERVER. A service record has also a match list of service classes it supports. When user looks for a certain service he sends a list of classes that specify the service. This list will be matched with the class list in the service record. This is why we have to add the class also to that list.
attr = sdp_set_class_attr(sdpSvcRec); sdp_add_class(attr, SDP_UUID_SDP_SERVER); sdp_add_uuid16_to_pattern(sdpSvcRec, SDP_UUID_SDP_SERVER);