BLE中SDP协议分析
BLE中SDP协议分析
在经典蓝牙中,蓝牙设备之间通过Service Discovery Protocol,SDP协议来实现客户端对服务端提供服务的信息获取。但要注意,在BLE中服务的发现是通过GATT层协议来做的。我也是研究半天才发现SDP跟BLE没什么关系,所以如果想了解SDP在BLE协议中起什么作用的可以不用看后面的内容了。但好歹研究了那么长时间,还是把研究到的东西写一下把。
简介
Service Record
在SDP中,服务端的服务被抽象为一个叫Service Record的数据库用于给客户端浏览使用。这个Service Record数据库里面包含着一条条叫做Service Attribute的数据表。
每条Service Attribute数据表包含两个值,ID和Value。ID是一个16bit数据,用于区分各个Attribute。Value是随ID不同而不同的数据单元,长度和结构不定。
用伪代码举例就是:
struct Service Attribute
{
uint16_t AttributeID;
DataElement Valuel
};
struct Service Attribute ServiceRecord[N];
Service Class
对于上述的Attribute ID,是由Service Class来定义的。Service Class代表了这是个什么服务类型,每个服务类由蓝牙协议专门划分了一个128位的UUID,但对于attribute ID,只截取了其中的16位。例如HID服务类的例子,有如下服务类和ID定义:
关于UUID,标准定义是128bit,但为了方便和传输,一般会将128bit中一般不变的位省略,只用其中会变的16bit和32bit。
数据元素
对于上述的Data Element,一般而言包含数据头和数据实体两个部分。其中数据头一个字节,包含TYPE DESCRIPTOR和SIZE DESCRIPTOR两个参数。
TYPE DESCRIPTOR
长度为5bit,代表了数据的类型,例如是整形还是字符串。
SIZE DESCRIPTOR
长度为3bit,代表了数据的长度
DATA ELEMENT
数据实体,类型和长度由数据头决定。
示例
如下为一个空类型数据,一个16bit整形数据,一个三个字节长度的字符数据的示例:
传输协议
PDU格式
SDP传输协议中的PDU格式如上所示,每段说明如下:
比较简单,就是区分下包类型,包ID号,和数据长度。
延续态
有些PDU包传的parameter可能比较多,一次传不完要传多次。为了方便下一次传输parameter能衔接上上一包的顺序,可以包PDU末尾添加延续态端,告诉对端下一包数据要衔接上一包。
错误处理
如果客户端发的请求包有问题,那么服务端就会答复错误处理响应。
服务搜索传输
服务搜索请求,主要用于客户端请求服务端是否有,有几个匹配的service。
在这里,客户端会预先知道自己想要查询的服务类UUID(16/32bit),并作为SearchPattern发送给服务端,服务端收到后会检查自己有没有匹配的服务,有多少个,然后返回给客户端。
- 示例
在spec中Vol 3, Part B APPENDIX B EXAMPLE SDP TRANSACTIONS的示例如下,PDF复制过来括号没办法对齐,建议直接看spec原文:
/* Sent from SDP Client to SDP server */
SDP_ServiceSearchRequest[15] {
PDUID[1] {
0x02
}
TransactionID[2] {
0xtttt
}
ParameterLength[2] {
0x000A
}
ServiceSearchPattern[7] {
DataElementSequence[7] {
0b00110 0b101 0x05
UUID[5] {
/* PrinterServiceClassID */
0b00011 0b010 0xpppppppp
}
}
}
MaximumServiceRecordCount[2] {
0x0003
}
ContinuationState[1] {
/* no continuation state */
0x00
}
}
/* Sent from SDP server to SDP client */
SDP_ServiceSearchResponse[18] {
PDUID[1] {
0x03
}
TransactionID[2] {
0xtttt
}
ParameterLength[2] {
0x000D
}
TotalServiceRecordCount[2] {
0x0002
}
CurrentServiceRecordCount[2] {
0x0002
}
ServiceRecordHandleList[8] {
/* print service 1 handle */
0xqqqqqqqq
/* print service 2 handle */
0xrrrrrrrr
}
ContinuationState[1] {
/* no continuation state */
0x00
}
}
其中,客户端发起请求的打印机服务类UUID假定为“0xpppppppp”,而服务端返回的两个打印机服务handle分别为“0xqqqqqqqq”和“0xrrrrrrrr”。 简单来讲就是客户端问服务端,你有没有打印机(UUID:0xpppppppp)的服务,然后服务端收到后检查了下,然后说我这有两个打印机服务。分别是handle1:“0xqqqqqqqq,和handle2:“0xrrrrrrrr”。
服务属性传输
服务属性请求,主要用于客户端请求服务端某个Service Record中的一个或多个attribute内容。
在这里,客户端会预先知道自己想要查询的service record的handle,以及想要查询的attribute的id list。服务端收到后会返回对应service record的attribute给客户端。
- 示例
/* Sent from SDP Client to SDP server */
SDP_ServiceAttributeRequest[17] {
PDUID[1] {
0x04
}
TransactionID[2] {
0xuuuu
}
ParameterLength[2] {
0x000C
}
ServiceRecordHandle[4] {
0xqqqqqqqq
}
MaximumAttributeByteCount[2] {
0x0080
}
AttributeIDList[5] {
DataElementSequence[5] {
0b00110 0b101 0x03
AttributeID[3] {
0b00001 0b001 0x0004
}
}
}
ContinuationState[1] {
/* no continuation state */
0x00
}
}
/* Sent from SDP server to SDP client */
SDP_ServiceAttributeResponse[38] {
PDUID[1] {
0x05
}
TransactionID[2] {
0xuuuu
}
ParameterLength[2] {
0x0021
}
AttributeListByteCount[2] {
0x001E
}
AttributeList[30] {
DataElementSequence[30] {
0b00110 0b101 0x1C
Attribute[28] {
AttributeID[3] {
0b00001 0b001 0x0004
}
AttributeValue[25] {
/* ProtocolDescriptorList */
DataElementSequence[25] {
0b00110 0b101 0x17
/* L2CAP protocol descriptor */
DataElementSequence[7] {
0b00110 0b101 0x05
UUID[5] {
/* L2CAP Protocol UUID */
0b00011 0b010 <32-bit L2CAP UUID>
}
}
/* RFCOMM protocol descriptor */
DataElementSequence[9] {
0b00110 0b101 0x07
UUID[5] {
/* RFCOMM Protocol UUID */
0b00011 0b010 <32-bit RFCOMM UUID>
}
/* parameter for server 2 */
Uint8[2] {
0b00001 0b000 0x02
}
}
/* PostscriptStream protocol descriptor */
DataElementSequence[7] {
0b00110 0b101 0x05
UUID[5] {
/* PostscriptStream Protocol UUID */
0b00011 0b010 <32-bit PostscriptStream UUID>
}
}
}
}
}
}
}
ContinuationState[1] {
/* no continuation state */
0x00
}
}
如上所示,这里客户端请求的service record的handle为“0xqqqqqqqq”,查询的attribute ID为“0x04”。即:
AttributeIDList[5] {
DataElementSequence[5] {
0b00110 0b101 0x03
AttributeID[3] {
0b00001 0b001 0x0004
}
}
}
随后服务端返回了对应service1和service2的对应attribute id的attribute value,内容为:
AttributeValue[25] {
/* ProtocolDescriptorList */
DataElementSequence[25] {
0b00110 0b101 0x17
/* L2CAP protocol descriptor */
DataElementSequence[7] {
0b00110 0b101 0x05
UUID[5] {
/* L2CAP Protocol UUID */
0b00011 0b010 <32-bit L2CAP UUID>
}
}
/* RFCOMM protocol descriptor */
DataElementSequence[9] {
0b00110 0b101 0x07
UUID[5] {
/* RFCOMM Protocol UUID */
0b00011 0b010 <32-bit RFCOMM UUID>
}
/* parameter for server 2 */
Uint8[2] {
0b00001 0b000 0x02
}
}
/* PostscriptStream protocol descriptor */
DataElementSequence[7] {
0b00110 0b101 0x05
UUID[5] {
/* PostscriptStream Protocol UUID */
0b00011 0b010 <32-bit PostscriptStream UUID>
}
}
}
}
}
}
}
服务搜索属性传输
服务搜索属性请求,上面的两个服务搜索和服务属性的合并版本。这里不作赘述,详情可了解spec原文。
服务属性定义
以下内容需要对照spec原文阅读,这里只做感想总结下描述。
通用属性定义
一般的attribute用于描述service class,有为了方便也设计了一些用来描述其他内容的attribute,例如上文提到的ServiceRecordHandle,就是专门用来描述服务记录的句柄。
“(服务发现)服务”服务类属性定义
原文有点拗口,我也没太搞清楚。大致是说这类属性是用来描述”服务发现“服务的attribute。这些属性和通用属性一样,但只在ServiceClassIDList包含ServiceDiscoveryServerServiceClassID时会生效。
”浏览群组描述符“服务类属性定义
一般前面讲的服务搜索都是要客户端已知服务端包含的服务Class 来进行的搜索,但也会有出现客户端不知道服务端有什么服务情况下去发现服务。这时候就会用这种属性了
总结
对于SDP协议,通信本身并不复杂。都是基本的request+respond机制。SDP协议的关键在于对于数据结构的定义上,搞清楚service record、attribute、service class的定义基本上就能把SDP协议给弄明白了。话说SDP也是够绕的,怪不得BLE就不用他了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现