BLE中SDP协议分析

发布时间 2023-08-14 15:26:09作者: 不回本不改名

BLE中SDP协议分析

​ 在经典蓝牙中,蓝牙设备之间通过Service Discovery Protocol,SDP协议来实现客户端对服务端提供服务的信息获取。但要注意,在BLE中服务的发现是通过GATT层协议来做的。我也是研究半天才发现SDP跟BLE没什么关系,所以如果想了解SDP在BLE协议中起什么作用的可以不用看后面的内容了。但好歹研究了那么长时间,还是把研究到的东西写一下把。

简介

Service Record

image

​ 在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 DESCRIPTORSIZE DESCRIPTOR两个参数。

TYPE DESCRIPTOR

长度为5bit,代表了数据的类型,例如是整形还是字符串。

SIZE DESCRIPTOR

长度为3bit,代表了数据的长度

DATA ELEMENT

数据实体,类型和长度由数据头决定。

示例

如下为一个空类型数据,一个16bit整形数据,一个三个字节长度的字符数据的示例:

传输协议

PDU格式

SDP传输协议中的PDU格式如上所示,每段说明如下:

比较简单,就是区分下包类型,包ID号,和数据长度。

延续态

有些PDU包传的parameter可能比较多,一次传不完要传多次。为了方便下一次传输parameter能衔接上上一包的顺序,可以包PDU末尾添加延续态端,告诉对端下一包数据要衔接上一包。

错误处理

如果客户端发的请求包有问题,那么服务端就会答复错误处理响应。

服务搜索传输

服务搜索请求,主要用于客户端请求服务端是否有,有几个匹配的service。

image

​ 在这里,客户端会预先知道自己想要查询的服务类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就不用他了。