1. SDP概念
我们想一想,两个陌生的设备(之前未有过交互)如何去发现对方支持什么服务呢?比如Host端的Controller怎么知道远程蓝牙设备是蓝牙耳机还是HID遥控器呢?我们需要一种协议,这种协议在蓝牙设备配对成功后,能让两个蓝牙设备通信,知道对方的能力(即支持什么Profile)。
SDP(Service Discovery Protocol)协议,它是一种服务发现的协议,在Core_v4.2 Vol 3, Part B,它规定了在服务器上面是如何存储的数据以及对方如何能够通过这个协议来获取到数据。它使用client-server架构,如下图:
比如:手机作为SDP Client,蓝牙耳机作为SDP Server,手机通过SDP协议向蓝牙耳机“咨询”它的能力。
SDP协议的重点与难点,在于理解服务、服务记录和属性的概念。为了辅助理解,可以用数据库做类比,服务为某个数据库(如MySQL),服务记录则是某类的数据,属性这是这类数据中具体的数据表格。SDP协议的作用则是发现对端所有的服务,以及该服务提供了哪些功能。
2. 相关概念
服务记录(ServiceRecord)
一个服务可能是任何能为另一个实体提供信息,采取行动或控制资源的实体。
SDP服务器维护的每一个服务信息都使用Service Record来表示。这个Service Record由若干ServiceAttribute组成,如下图所示:
服务属性(Service Attribute)
服务属性由两部分组成,attribute ID和attribute value组成。其结构如下图所示:
Attribute ID是一个16bits的无符号整形,用于区分各个Attribute,协议里面Core_v4.2 Vol 3, Part B 5 SERVICE ATTRIBUTE DEFINITIONS已经定义了很多Attribute和它们的Attribute ID。
attribute value是根据attribute ID的数据单元(Data Element),它的格式会因Attribute ID的不同而不同。
正因为SDP Service Record由许多Attribute组成,所以,可以把Record看成AtributeList,这在后面的SDP resposne中会常用到。
服务类(Service class)
Service Record只是一个service在软件或硬件上的表现形式(或称存储形式),而一个蓝牙设备支持的SDP service可能有多个,使用Service class来描述一个service(类似于面向对象,Service class是一个抽象类,而一个特定的service就是一个具体实例,而Service Record说明这个对象的数据是以Attribute列表的形式存储起来的)。
SDP协议规定了Service class有以下几个“类成员变量”(下面代码只是大概,并不准确):
struct Attribute{
usigned16 AttributeID;
DataElement Value; // DataElement的定义因AttributeID不同而不同
};
Class Service{ //下面列举一些服务的属性,一个服务可能只实现其中的某些属性。
Attribute ServiceClassIDList;
Attribute ServiceID;
Attribute ProtocolDescriptorList;
Attribute BluetoothProfileDescriptorList;
Attribute ServiceName;
Attribute ServiceDescription;
......
};
Class AttributeList{ // AttributeList就是一个Service Record
Attribute ServiceRecordHandle; //具有唯一的32bit的Handle
class Service service;
}
SDP规定使用service class identifier来区分不同的Service class。SDP协议规定这个值是一个UUID——唯一标识符,它是128bit的数字,但为了减少存储和传输负担,协议里面规定只有高16bit(以后可能有32bit的)用来区分Service Class,其它bit的值固定。另外,有些常用的服务类已经预定了UUID,参考文档《16-bit UUID Numbers Document.pdf》。ServiceClassIDList成员可保存一个或多个service class identifier,它表示所属的Service适用于一个或多个service class。
此外,有个特殊的Service class,叫BrowseGroupDescriptor。它的作用是方便Client通过Brower。通常,Client根据服务的某些期望UUID搜索服务。然而,有时需要知道SDP服务器的服务记录描述了哪些类型的服务,这个查询过程称为浏览。这个过程依赖一个特殊的Service class,叫BrowseGroupDescriptor。本文不展开说明了。
例子:
数据单元(Data Element)
sdp协议数据包(即attribute value)是数据单元的集合,一个数据包中可能包含一个或多个数据单元。每个数据包的头部都是一个“type descriptor(类型描述符)+Size descriptor”组成。
type descriptor占一个字节,由两部分组成:type descriptor(high 5-bits) 和size descriptor(low 3-bits):
上图中,第一列说明了Type descriptor可以取的值,目前之定义了0~8,第二列说明了不同的 Type descriptor会有不同的size descriptor值,比如Type descriptor为3,则size descriptor值只能取“1,2,4”中的一个值,第三列是这个Type的描述。
size descriptor描述如下:
因Type descriptor限制了size descriptor的取值,那size descriptor各个值得意思如上图。
实际运用可以参考如下的例子:
对上图三个Data Element的解释:
A. 这是个“空”的Data Element,Tpye为0,要求Size域的值只能为“0”,表示后面没有数据。
B. 这是一个函数16-bit的整型数,Type为2,表示一个有符号的二进制数,Size域的值为1,表示后面数据长度是2 byte。
C. 这是一个字符串“Hat”,Type为4,表示一个文本字符串,Size域的值为5,意思是后面是一个8bit的无符号数字,它说明了后面的字符串长度,比如8bit的值为3,即后面的字符串长度为3 Byte。
PDU报文格式
SDP协议的数据使用网络字节序,即大端字节序,与底层HCI、L2cap协议有所区别,需要特别注意!
其协议头部由三部分组成:PDU ID、Transaction ID和Parameter Length组成,Parameter则是上文所述的一个个数据单元。其具体的描述如下:
其中PDU ID表示协议包的类型,类型种类如下图所示:
TransactionID:用以区别其他的Transaction。大概的意思是Controller发送一个SDP请求后,在没有得到远程蓝牙设备回复之前,Controller又需要发送一个SDP请求,这时,PDU ID可能和上一次是一样的,所以需要用TransactionID来区分这是另一个请求。远程蓝牙设备在回复时,也需要加上TransactionID,标识它回复的是哪个请求。
另外还有一个概念Continuation State:当SDP 服务器返回client 结果的时候,如果数据太大,那么就会分包,下面额度变量就是分包的标志,如果部分包,其值为0
交互流程
根据PUD id 的不同,交互的流程也不同,但是思路都是一样,都是CS的架构
这里我们对ServiceSearchAttributeRequest PDU的request和response做简要说明,其他有兴趣的可以自行参考核心规范。Core_v4.2 Vol 3, Part B 4.7 ServiceSearchAttributeRequest 一次服务发现交互事务逻辑如下图所示,Client端可认为是手机,Server端可任务是蓝牙耳机:
1. SDP_ ServiceSearchAttributeRequest PDU
首先由client发起请求Service Search Attribute Request,描述如下
这个命令有4个参数,也就是说PDU里面除了报文头部,后面跟随四个参数,每个参数的长度可能不一样。
从btsnoop中摘取一次Service Search Request进行数据分析,这里只针对sdp进行解析:
PDU ID=0x06
TransactionID=0x0000
ParameterLength=0x0013,即19Byte。
接下来描述的是第一个参数ServiceSearchPattern——35 03 19 11 0B
DATA Element header =0x35,二进制表示为00110101b,type=00110b=0x06,标识后面的数据是一个Data element(说明DATA Element可以嵌套);size index=101b=0x05,表示后面1Byte是一个额外的字节描述后续字节长度。
Additional size(额外字节)=0x03,意思是后面跟着大小为3个字节的DATA Element——19 11 0B,这个DATA Element同样有header=0x19,二进制表示0x00011001,type=00011b=0x03,标识后面数据是一个UUID,UUID值=0x110B(注意SDP是大端格式),查16-bit UUID Numbers Document.pdf,UUID的别名是AudioSink。
接下来描述的是第二个参数MaximumAttributeByteCount(Attribute能返回的最大字节数),两个字节,0x0290=656。
第三个参数AttributeIDList——35 09 09 00 01 09 00 04 09 00 09。属性列表的数据是包含一个或多个Data element的列表,每个Data element里面标识了Service Search Request的attribute ID。这样在命令结束后,远程蓝牙设备会返回attribute ID相关的服务信息。
Data element header:=0x35,二进制表示为00110101b,type=00110b=0x06,标识后面的数据是一个Data element;size index=101b=0x05,表示后面1Byte是一个额外的字节描述后续字节长度。
Additional size(额外字节)=0x09,意思是后面跟着大小为9个字节的Data element(有三个)。
Data element header:=0x09,二进制表示为00001001b,type=00001b=0x01,标识后面的数据是一个整型数;size index=001b=0x01,表示后面2Byte都是整型数的值,分别是0x00,0x01。即属性ID=0x0001,是ServiceClassIDList Attribute。
Data element header:=0x09,二进制表示为00001001b,type=00001b=0x01,标识后面的数据是一个整型数;size index=001b=0x01,表示后面2Byte都是整型数的值,分别是0x00,0x04。即属性ID=0x0004,是ProtocolDescriptorList Attribute。
Data element header:=0x09,二进制表示为00001001b,type=00001b=0x01,标识后面的数据是一个整型数;size index=001b=0x01,表示后面2Byte都是整型数的值,分别是0x00,0x09。即属性ID=0x0009,是BluetoothProfileDescriptorList Attribute。
第四个参数——ContinuationState
是一个8-bit的数字,本例子中为0x00。
2. SDP_ServiceSearchAttributeResponse PDU
当服务器收到请求之后,给出对应的Service Search Attribute Response描述如下:
如上图,分别返回了三个指定属性ID的信息。